Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/quite/humla-spongycastle.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/pg/src
diff options
context:
space:
mode:
authorDavid Hook <dgh@cryptoworkshop.com>2013-05-31 11:07:45 +0400
committerDavid Hook <dgh@cryptoworkshop.com>2013-05-31 11:07:45 +0400
commit2b976f5364cfdbc37d3086019d93483c983eb80b (patch)
treecb846af3fd1d43f9c2562a1fb2d06b997ad8f229 /pg/src
parent5f714bd92fbd780d22406f4bc3681be005f6f04a (diff)
initial reshuffle
Diffstat (limited to 'pg/src')
-rw-r--r--pg/src/main/java/org/bouncycastle/apache/bzip2/BZip2Constants.java100
-rw-r--r--pg/src/main/java/org/bouncycastle/apache/bzip2/CBZip2InputStream.java848
-rw-r--r--pg/src/main/java/org/bouncycastle/apache/bzip2/CBZip2OutputStream.java1651
-rw-r--r--pg/src/main/java/org/bouncycastle/apache/bzip2/CRC.java131
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/ArmoredInputStream.java473
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/ArmoredOutputStream.java411
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/BCPGInputStream.java391
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/BCPGKey.java24
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/BCPGObject.java24
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/BCPGOutputStream.java361
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/CRC24.java37
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/CompressedDataPacket.java31
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/CompressionAlgorithmTags.java12
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/ContainedPacket.java26
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/DSAPublicBCPGKey.java116
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/DSASecretBCPGKey.java82
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/ElGamalPublicBCPGKey.java93
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/ElGamalSecretBCPGKey.java79
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/ExperimentalPacket.java46
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/HashAlgorithmTags.java20
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/InputStreamPacket.java26
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/LiteralDataPacket.java74
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/MPInteger.java62
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/MarkerPacket.java28
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/ModDetectionCodePacket.java45
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/OnePassSignaturePacket.java115
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/OutputStreamPacket.java18
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/Packet.java9
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/PacketTags.java31
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/PublicKeyAlgorithmTags.java29
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/PublicKeyEncSessionPacket.java112
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/PublicKeyPacket.java126
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/PublicSubkeyPacket.java40
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/RSAPublicBCPGKey.java91
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/RSASecretBCPGKey.java176
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/S2K.java151
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java185
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/SecretSubkeyPacket.java58
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/SignaturePacket.java515
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/SignatureSubpacket.java81
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/SignatureSubpacketInputStream.java123
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/SignatureSubpacketTags.java32
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/SymmetricEncDataPacket.java14
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/SymmetricEncIntegrityPacket.java20
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyAlgorithmTags.java19
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java90
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/TrustPacket.java48
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/UserAttributePacket.java60
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/UserAttributeSubpacket.java91
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/UserAttributeSubpacketInputStream.java116
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/UserAttributeSubpacketTags.java9
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/UserIDPacket.java39
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/attr/ImageAttribute.java77
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/EmbeddedSignature.java18
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/Exportable.java46
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/Features.java98
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/IssuerKeyID.java50
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/KeyExpirationTime.java50
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/KeyFlags.java73
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/NotationData.java104
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/PreferredAlgorithms.java59
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/PrimaryUserID.java46
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/Revocable.java46
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationKey.java52
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationKeyTags.java8
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationReason.java51
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationReasonTags.java12
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/SignatureCreationTime.java48
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/SignatureExpirationTime.java48
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/SignerUserID.java50
-rw-r--r--pg/src/main/java/org/bouncycastle/bcpg/sig/TrustSignature.java48
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPCompressedData.java143
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPCompressedDataGenerator.java201
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPDataValidationException.java17
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedData.java147
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java537
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataList.java75
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPException.java35
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPKeyFlags.java19
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java148
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRing.java125
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java272
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPKeyValidationException.java16
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPLiteralData.java96
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPLiteralDataGenerator.java202
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPMarker.java34
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPObjectFactory.java151
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignature.java266
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignatureList.java40
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPPBEEncryptedData.java180
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPPrivateKey.java138
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java964
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java262
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyRing.java273
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyRingCollection.java369
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java937
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java480
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRingCollection.java367
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java564
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureGenerator.java574
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureList.java40
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java197
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java277
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPUserAttributeSubpacketVector.java93
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPUserAttributeSubpacketVectorGenerator.java27
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPUtil.java376
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java286
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/StreamGenerator.java9
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/WrappedGeneratorStream.java46
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/examples/ByteArrayHandler.java206
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/examples/ClearSignedFileProcessor.java390
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java139
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/examples/DetachedSignatureProcessor.java198
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/examples/DirectKeySignature.java135
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/examples/KeyBasedFileProcessor.java279
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/examples/KeyBasedLargeFileProcessor.java283
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/examples/PBEFileProcessor.java214
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/examples/PGPExampleUtil.java153
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/examples/PubringDump.java102
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/examples/RSAKeyPairGenerator.java114
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/examples/SignedFileProcessor.java215
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/KeyFingerPrintCalculator.java10
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PBEDataDecryptorFactory.java26
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java91
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyDecryptor.java31
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptor.java104
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSigner.java20
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSignerBuilder.java10
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentVerifier.java20
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentVerifierBuilder.java10
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java9
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataDecryptor.java12
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataDecryptorFactory.java9
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataDecryptorProvider.java5
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataEncryptor.java12
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataEncryptorBuilder.java15
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDigestCalculator.java35
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDigestCalculatorProvider.java9
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java10
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PGPUtil.java216
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java12
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java78
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcImplProvider.java138
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java68
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java67
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java97
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java43
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java142
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java98
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java75
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java118
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java82
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyConverter.java183
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPair.java33
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java109
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java68
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcUtil.java75
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java68
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/bc/SignerOutputStream.java35
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java72
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java156
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java112
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java119
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java258
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java34
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java34
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java99
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java132
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java100
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java180
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java146
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java185
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java93
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/OperatorHelper.java171
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/PGPUtil.java149
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java81
-rw-r--r--pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/SignatureOutputStream.java56
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/examples/test/AllTests.java415
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/AllTests.java46
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPDSAElGamalTest.java564
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPDSATest.java633
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPKeyRingTest.java2362
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPPBETest.java400
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPRSATest.java1376
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/DSA2Test.java290
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/PGPArmoredTest.java255
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/PGPClearSignedSignatureTest.java454
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/PGPCompressionTest.java143
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/PGPDSAElGamalTest.java552
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/PGPDSATest.java628
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyRingTest.java2610
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/PGPMarkerTest.java105
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/PGPNoPrivateKeyTest.java167
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/PGPPBETest.java396
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/PGPPacketTest.java103
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/PGPRSATest.java1471
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/PGPSignatureTest.java780
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/PGPUnicodeTest.java183
-rw-r--r--pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java46
199 files changed, 38383 insertions, 0 deletions
diff --git a/pg/src/main/java/org/bouncycastle/apache/bzip2/BZip2Constants.java b/pg/src/main/java/org/bouncycastle/apache/bzip2/BZip2Constants.java
new file mode 100644
index 00000000..e86bdeeb
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/apache/bzip2/BZip2Constants.java
@@ -0,0 +1,100 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ *
+ */
+
+/*
+ * This package is based on the work done by Keiron Liddle, Aftex Software
+ * <keiron@aftexsw.com> to whom the Ant project is very grateful for his
+ * great code.
+ */
+
+package org.bouncycastle.apache.bzip2;
+
+/**
+ * Base class for both the compress and decompress classes.
+ * Holds common arrays, and static data.
+ *
+ * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a>
+ */
+public interface BZip2Constants {
+
+ int baseBlockSize = 100000;
+ int MAX_ALPHA_SIZE = 258;
+ int MAX_CODE_LEN = 23;
+ int RUNA = 0;
+ int RUNB = 1;
+ int N_GROUPS = 6;
+ int G_SIZE = 50;
+ int N_ITERS = 4;
+ int MAX_SELECTORS = (2 + (900000 / G_SIZE));
+ int NUM_OVERSHOOT_BYTES = 20;
+
+ int[] rNums = {
+ 619, 720, 127, 481, 931, 816, 813, 233, 566, 247,
+ 985, 724, 205, 454, 863, 491, 741, 242, 949, 214,
+ 733, 859, 335, 708, 621, 574, 73, 654, 730, 472,
+ 419, 436, 278, 496, 867, 210, 399, 680, 480, 51,
+ 878, 465, 811, 169, 869, 675, 611, 697, 867, 561,
+ 862, 687, 507, 283, 482, 129, 807, 591, 733, 623,
+ 150, 238, 59, 379, 684, 877, 625, 169, 643, 105,
+ 170, 607, 520, 932, 727, 476, 693, 425, 174, 647,
+ 73, 122, 335, 530, 442, 853, 695, 249, 445, 515,
+ 909, 545, 703, 919, 874, 474, 882, 500, 594, 612,
+ 641, 801, 220, 162, 819, 984, 589, 513, 495, 799,
+ 161, 604, 958, 533, 221, 400, 386, 867, 600, 782,
+ 382, 596, 414, 171, 516, 375, 682, 485, 911, 276,
+ 98, 553, 163, 354, 666, 933, 424, 341, 533, 870,
+ 227, 730, 475, 186, 263, 647, 537, 686, 600, 224,
+ 469, 68, 770, 919, 190, 373, 294, 822, 808, 206,
+ 184, 943, 795, 384, 383, 461, 404, 758, 839, 887,
+ 715, 67, 618, 276, 204, 918, 873, 777, 604, 560,
+ 951, 160, 578, 722, 79, 804, 96, 409, 713, 940,
+ 652, 934, 970, 447, 318, 353, 859, 672, 112, 785,
+ 645, 863, 803, 350, 139, 93, 354, 99, 820, 908,
+ 609, 772, 154, 274, 580, 184, 79, 626, 630, 742,
+ 653, 282, 762, 623, 680, 81, 927, 626, 789, 125,
+ 411, 521, 938, 300, 821, 78, 343, 175, 128, 250,
+ 170, 774, 972, 275, 999, 639, 495, 78, 352, 126,
+ 857, 956, 358, 619, 580, 124, 737, 594, 701, 612,
+ 669, 112, 134, 694, 363, 992, 809, 743, 168, 974,
+ 944, 375, 748, 52, 600, 747, 642, 182, 862, 81,
+ 344, 805, 988, 739, 511, 655, 814, 334, 249, 515,
+ 897, 955, 664, 981, 649, 113, 974, 459, 893, 228,
+ 433, 837, 553, 268, 926, 240, 102, 654, 459, 51,
+ 686, 754, 806, 760, 493, 403, 415, 394, 687, 700,
+ 946, 670, 656, 610, 738, 392, 760, 799, 887, 653,
+ 978, 321, 576, 617, 626, 502, 894, 679, 243, 440,
+ 680, 879, 194, 572, 640, 724, 926, 56, 204, 700,
+ 707, 151, 457, 449, 797, 195, 791, 558, 945, 679,
+ 297, 59, 87, 824, 713, 663, 412, 693, 342, 606,
+ 134, 108, 571, 364, 631, 212, 174, 643, 304, 329,
+ 343, 97, 430, 751, 497, 314, 983, 374, 822, 928,
+ 140, 206, 73, 263, 980, 736, 876, 478, 430, 305,
+ 170, 514, 364, 692, 829, 82, 855, 953, 676, 246,
+ 369, 970, 294, 750, 807, 827, 150, 790, 288, 923,
+ 804, 378, 215, 828, 592, 281, 565, 555, 710, 82,
+ 896, 831, 547, 261, 524, 462, 293, 465, 502, 56,
+ 661, 821, 976, 991, 658, 869, 905, 758, 745, 193,
+ 768, 550, 608, 933, 378, 286, 215, 979, 792, 961,
+ 61, 688, 793, 644, 986, 403, 106, 366, 905, 644,
+ 372, 567, 466, 434, 645, 210, 389, 550, 919, 135,
+ 780, 773, 635, 389, 707, 100, 626, 958, 165, 504,
+ 920, 176, 193, 713, 857, 265, 203, 50, 668, 108,
+ 645, 990, 626, 197, 510, 357, 358, 850, 858, 364,
+ 936, 638
+ };
+}
diff --git a/pg/src/main/java/org/bouncycastle/apache/bzip2/CBZip2InputStream.java b/pg/src/main/java/org/bouncycastle/apache/bzip2/CBZip2InputStream.java
new file mode 100644
index 00000000..08d05e7d
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/apache/bzip2/CBZip2InputStream.java
@@ -0,0 +1,848 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ *
+ */
+
+/*
+ * This package is based on the work done by Keiron Liddle, Aftex Software
+ * <keiron@aftexsw.com> to whom the Ant project is very grateful for his
+ * great code.
+ */
+package org.bouncycastle.apache.bzip2;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * An input stream that decompresses from the BZip2 format (with the file
+ * header chars) to be read as any other stream.
+ *
+ * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a>
+ *
+ * <b>NB:</b> note this class has been modified to read the leading BZ from the
+ * start of the BZIP2 stream to make it compatible with other PGP programs.
+ */
+public class CBZip2InputStream extends InputStream implements BZip2Constants {
+ private static void cadvise() {
+ System.out.println("CRC Error");
+ //throw new CCoruptionError();
+ }
+
+// private static void badBGLengths() {
+// cadvise();
+// }
+//
+// private static void bitStreamEOF() {
+// cadvise();
+// }
+
+ private static void compressedStreamEOF() {
+ cadvise();
+ }
+
+ private void makeMaps() {
+ int i;
+ nInUse = 0;
+ for (i = 0; i < 256; i++) {
+ if (inUse[i]) {
+ seqToUnseq[nInUse] = (char) i;
+ unseqToSeq[i] = (char) nInUse;
+ nInUse++;
+ }
+ }
+ }
+
+ /*
+ index of the last char in the block, so
+ the block size == last + 1.
+ */
+ private int last;
+
+ /*
+ index in zptr[] of original string after sorting.
+ */
+ private int origPtr;
+
+ /*
+ always: in the range 0 .. 9.
+ The current block size is 100000 * this number.
+ */
+ private int blockSize100k;
+
+ private boolean blockRandomised;
+
+ private int bsBuff;
+ private int bsLive;
+ private CRC mCrc = new CRC();
+
+ private boolean[] inUse = new boolean[256];
+ private int nInUse;
+
+ private char[] seqToUnseq = new char[256];
+ private char[] unseqToSeq = new char[256];
+
+ private char[] selector = new char[MAX_SELECTORS];
+ private char[] selectorMtf = new char[MAX_SELECTORS];
+
+ private int[] tt;
+ private char[] ll8;
+
+ /*
+ freq table collected to save a pass over the data
+ during decompression.
+ */
+ private int[] unzftab = new int[256];
+
+ private int[][] limit = new int[N_GROUPS][MAX_ALPHA_SIZE];
+ private int[][] base = new int[N_GROUPS][MAX_ALPHA_SIZE];
+ private int[][] perm = new int[N_GROUPS][MAX_ALPHA_SIZE];
+ private int[] minLens = new int[N_GROUPS];
+
+ private InputStream bsStream;
+
+ private boolean streamEnd = false;
+
+ private int currentChar = -1;
+
+ private static final int START_BLOCK_STATE = 1;
+ private static final int RAND_PART_A_STATE = 2;
+ private static final int RAND_PART_B_STATE = 3;
+ private static final int RAND_PART_C_STATE = 4;
+ private static final int NO_RAND_PART_A_STATE = 5;
+ private static final int NO_RAND_PART_B_STATE = 6;
+ private static final int NO_RAND_PART_C_STATE = 7;
+
+ private int currentState = START_BLOCK_STATE;
+
+ private int storedBlockCRC, storedCombinedCRC;
+ private int computedBlockCRC, computedCombinedCRC;
+
+ int i2, count, chPrev, ch2;
+ int i, tPos;
+ int rNToGo = 0;
+ int rTPos = 0;
+ int j2;
+ char z;
+
+ public CBZip2InputStream(InputStream zStream)
+ throws IOException
+ {
+ ll8 = null;
+ tt = null;
+ bsSetStream(zStream);
+ initialize();
+ initBlock();
+ setupBlock();
+ }
+
+ public int read() {
+ if (streamEnd) {
+ return -1;
+ } else {
+ int retChar = currentChar;
+ switch(currentState) {
+ case START_BLOCK_STATE:
+ break;
+ case RAND_PART_A_STATE:
+ break;
+ case RAND_PART_B_STATE:
+ setupRandPartB();
+ break;
+ case RAND_PART_C_STATE:
+ setupRandPartC();
+ break;
+ case NO_RAND_PART_A_STATE:
+ break;
+ case NO_RAND_PART_B_STATE:
+ setupNoRandPartB();
+ break;
+ case NO_RAND_PART_C_STATE:
+ setupNoRandPartC();
+ break;
+ default:
+ break;
+ }
+ return retChar;
+ }
+ }
+
+ private void initialize() throws IOException {
+ char magic3, magic4;
+ magic3 = bsGetUChar();
+ magic4 = bsGetUChar();
+ if (magic3 != 'B' && magic4 != 'Z')
+ {
+ throw new IOException("Not a BZIP2 marked stream");
+ }
+ magic3 = bsGetUChar();
+ magic4 = bsGetUChar();
+ if (magic3 != 'h' || magic4 < '1' || magic4 > '9') {
+ bsFinishedWithStream();
+ streamEnd = true;
+ return;
+ }
+
+ setDecompressStructureSizes(magic4 - '0');
+ computedCombinedCRC = 0;
+ }
+
+ private void initBlock() {
+ char magic1, magic2, magic3, magic4;
+ char magic5, magic6;
+ magic1 = bsGetUChar();
+ magic2 = bsGetUChar();
+ magic3 = bsGetUChar();
+ magic4 = bsGetUChar();
+ magic5 = bsGetUChar();
+ magic6 = bsGetUChar();
+ if (magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45
+ && magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90) {
+ complete();
+ return;
+ }
+
+ if (magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59
+ || magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59) {
+ badBlockHeader();
+ streamEnd = true;
+ return;
+ }
+
+ storedBlockCRC = bsGetInt32();
+
+ if (bsR(1) == 1) {
+ blockRandomised = true;
+ } else {
+ blockRandomised = false;
+ }
+
+ // currBlockNo++;
+ getAndMoveToFrontDecode();
+
+ mCrc.initialiseCRC();
+ currentState = START_BLOCK_STATE;
+ }
+
+ private void endBlock() {
+ computedBlockCRC = mCrc.getFinalCRC();
+ /* A bad CRC is considered a fatal error. */
+ if (storedBlockCRC != computedBlockCRC) {
+ crcError();
+ }
+
+ computedCombinedCRC = (computedCombinedCRC << 1)
+ | (computedCombinedCRC >>> 31);
+ computedCombinedCRC ^= computedBlockCRC;
+ }
+
+ private void complete() {
+ storedCombinedCRC = bsGetInt32();
+ if (storedCombinedCRC != computedCombinedCRC) {
+ crcError();
+ }
+
+ bsFinishedWithStream();
+ streamEnd = true;
+ }
+
+ private static void blockOverrun() {
+ cadvise();
+ }
+
+ private static void badBlockHeader() {
+ cadvise();
+ }
+
+ private static void crcError() {
+ cadvise();
+ }
+
+ private void bsFinishedWithStream() {
+ try {
+ if (this.bsStream != null) {
+ if (this.bsStream != System.in) {
+ this.bsStream.close();
+ this.bsStream = null;
+ }
+ }
+ } catch (IOException ioe) {
+ //ignore
+ }
+ }
+
+ private void bsSetStream(InputStream f) {
+ bsStream = f;
+ bsLive = 0;
+ bsBuff = 0;
+ }
+
+ private int bsR(int n) {
+ int v;
+ while (bsLive < n) {
+ int zzi;
+ char thech = 0;
+ try {
+ thech = (char) bsStream.read();
+ } catch (IOException e) {
+ compressedStreamEOF();
+ }
+ if (thech == -1) {
+ compressedStreamEOF();
+ }
+ zzi = thech;
+ bsBuff = (bsBuff << 8) | (zzi & 0xff);
+ bsLive += 8;
+ }
+
+ v = (bsBuff >> (bsLive - n)) & ((1 << n) - 1);
+ bsLive -= n;
+ return v;
+ }
+
+ private char bsGetUChar() {
+ return (char) bsR(8);
+ }
+
+ private int bsGetint() {
+ int u = 0;
+ u = (u << 8) | bsR(8);
+ u = (u << 8) | bsR(8);
+ u = (u << 8) | bsR(8);
+ u = (u << 8) | bsR(8);
+ return u;
+ }
+
+ private int bsGetIntVS(int numBits) {
+ return (int) bsR(numBits);
+ }
+
+ private int bsGetInt32() {
+ return (int) bsGetint();
+ }
+
+ private void hbCreateDecodeTables(int[] limit, int[] base,
+ int[] perm, char[] length,
+ int minLen, int maxLen, int alphaSize) {
+ int pp, i, j, vec;
+
+ pp = 0;
+ for (i = minLen; i <= maxLen; i++) {
+ for (j = 0; j < alphaSize; j++) {
+ if (length[j] == i) {
+ perm[pp] = j;
+ pp++;
+ }
+ }
+ }
+
+ for (i = 0; i < MAX_CODE_LEN; i++) {
+ base[i] = 0;
+ }
+ for (i = 0; i < alphaSize; i++) {
+ base[length[i] + 1]++;
+ }
+
+ for (i = 1; i < MAX_CODE_LEN; i++) {
+ base[i] += base[i - 1];
+ }
+
+ for (i = 0; i < MAX_CODE_LEN; i++) {
+ limit[i] = 0;
+ }
+ vec = 0;
+
+ for (i = minLen; i <= maxLen; i++) {
+ vec += (base[i + 1] - base[i]);
+ limit[i] = vec - 1;
+ vec <<= 1;
+ }
+ for (i = minLen + 1; i <= maxLen; i++) {
+ base[i] = ((limit[i - 1] + 1) << 1) - base[i];
+ }
+ }
+
+ private void recvDecodingTables() {
+ char len[][] = new char[N_GROUPS][MAX_ALPHA_SIZE];
+ int i, j, t, nGroups, nSelectors, alphaSize;
+ int minLen, maxLen;
+ boolean[] inUse16 = new boolean[16];
+
+ /* Receive the mapping table */
+ for (i = 0; i < 16; i++) {
+ if (bsR(1) == 1) {
+ inUse16[i] = true;
+ } else {
+ inUse16[i] = false;
+ }
+ }
+
+ for (i = 0; i < 256; i++) {
+ inUse[i] = false;
+ }
+
+ for (i = 0; i < 16; i++) {
+ if (inUse16[i]) {
+ for (j = 0; j < 16; j++) {
+ if (bsR(1) == 1) {
+ inUse[i * 16 + j] = true;
+ }
+ }
+ }
+ }
+
+ makeMaps();
+ alphaSize = nInUse + 2;
+
+ /* Now the selectors */
+ nGroups = bsR(3);
+ nSelectors = bsR(15);
+ for (i = 0; i < nSelectors; i++) {
+ j = 0;
+ while (bsR(1) == 1) {
+ j++;
+ }
+ selectorMtf[i] = (char) j;
+ }
+
+ /* Undo the MTF values for the selectors. */
+ {
+ char[] pos = new char[N_GROUPS];
+ char tmp, v;
+ for (v = 0; v < nGroups; v++) {
+ pos[v] = v;
+ }
+
+ for (i = 0; i < nSelectors; i++) {
+ v = selectorMtf[i];
+ tmp = pos[v];
+ while (v > 0) {
+ pos[v] = pos[v - 1];
+ v--;
+ }
+ pos[0] = tmp;
+ selector[i] = tmp;
+ }
+ }
+
+ /* Now the coding tables */
+ for (t = 0; t < nGroups; t++) {
+ int curr = bsR(5);
+ for (i = 0; i < alphaSize; i++) {
+ while (bsR(1) == 1) {
+ if (bsR(1) == 0) {
+ curr++;
+ } else {
+ curr--;
+ }
+ }
+ len[t][i] = (char) curr;
+ }
+ }
+
+ /* Create the Huffman decoding tables */
+ for (t = 0; t < nGroups; t++) {
+ minLen = 32;
+ maxLen = 0;
+ for (i = 0; i < alphaSize; i++) {
+ if (len[t][i] > maxLen) {
+ maxLen = len[t][i];
+ }
+ if (len[t][i] < minLen) {
+ minLen = len[t][i];
+ }
+ }
+ hbCreateDecodeTables(limit[t], base[t], perm[t], len[t], minLen,
+ maxLen, alphaSize);
+ minLens[t] = minLen;
+ }
+ }
+
+ private void getAndMoveToFrontDecode() {
+ char[] yy = new char[256];
+ int i, j, nextSym, limitLast;
+ int EOB, groupNo, groupPos;
+
+ limitLast = baseBlockSize * blockSize100k;
+ origPtr = bsGetIntVS(24);
+
+ recvDecodingTables();
+ EOB = nInUse + 1;
+ groupNo = -1;
+ groupPos = 0;
+
+ /*
+ Setting up the unzftab entries here is not strictly
+ necessary, but it does save having to do it later
+ in a separate pass, and so saves a block's worth of
+ cache misses.
+ */
+ for (i = 0; i <= 255; i++) {
+ unzftab[i] = 0;
+ }
+
+ for (i = 0; i <= 255; i++) {
+ yy[i] = (char) i;
+ }
+
+ last = -1;
+
+ {
+ int zt, zn, zvec, zj;
+ if (groupPos == 0) {
+ groupNo++;
+ groupPos = G_SIZE;
+ }
+ groupPos--;
+ zt = selector[groupNo];
+ zn = minLens[zt];
+ zvec = bsR(zn);
+ while (zvec > limit[zt][zn]) {
+ zn++;
+ {
+ {
+ while (bsLive < 1) {
+ int zzi;
+ char thech = 0;
+ try {
+ thech = (char) bsStream.read();
+ } catch (IOException e) {
+ compressedStreamEOF();
+ }
+ if (thech == -1) {
+ compressedStreamEOF();
+ }
+ zzi = thech;
+ bsBuff = (bsBuff << 8) | (zzi & 0xff);
+ bsLive += 8;
+ }
+ }
+ zj = (bsBuff >> (bsLive - 1)) & 1;
+ bsLive--;
+ }
+ zvec = (zvec << 1) | zj;
+ }
+ nextSym = perm[zt][zvec - base[zt][zn]];
+ }
+
+ while (true) {
+
+ if (nextSym == EOB) {
+ break;
+ }
+
+ if (nextSym == RUNA || nextSym == RUNB) {
+ char ch;
+ int s = -1;
+ int N = 1;
+ do {
+ if (nextSym == RUNA) {
+ s = s + (0 + 1) * N;
+ } else if (nextSym == RUNB) {
+ s = s + (1 + 1) * N;
+ }
+ N = N * 2;
+ {
+ int zt, zn, zvec, zj;
+ if (groupPos == 0) {
+ groupNo++;
+ groupPos = G_SIZE;
+ }
+ groupPos--;
+ zt = selector[groupNo];
+ zn = minLens[zt];
+ zvec = bsR(zn);
+ while (zvec > limit[zt][zn]) {
+ zn++;
+ {
+ {
+ while (bsLive < 1) {
+ int zzi;
+ char thech = 0;
+ try {
+ thech = (char) bsStream.read();
+ } catch (IOException e) {
+ compressedStreamEOF();
+ }
+ if (thech == -1) {
+ compressedStreamEOF();
+ }
+ zzi = thech;
+ bsBuff = (bsBuff << 8) | (zzi & 0xff);
+ bsLive += 8;
+ }
+ }
+ zj = (bsBuff >> (bsLive - 1)) & 1;
+ bsLive--;
+ }
+ zvec = (zvec << 1) | zj;
+ }
+ nextSym = perm[zt][zvec - base[zt][zn]];
+ }
+ } while (nextSym == RUNA || nextSym == RUNB);
+
+ s++;
+ ch = seqToUnseq[yy[0]];
+ unzftab[ch] += s;
+
+ while (s > 0) {
+ last++;
+ ll8[last] = ch;
+ s--;
+ }
+
+ if (last >= limitLast) {
+ blockOverrun();
+ }
+ continue;
+ } else {
+ char tmp;
+ last++;
+ if (last >= limitLast) {
+ blockOverrun();
+ }
+
+ tmp = yy[nextSym - 1];
+ unzftab[seqToUnseq[tmp]]++;
+ ll8[last] = seqToUnseq[tmp];
+
+ /*
+ This loop is hammered during decompression,
+ hence the unrolling.
+
+ for (j = nextSym-1; j > 0; j--) yy[j] = yy[j-1];
+ */
+
+ j = nextSym - 1;
+ for (; j > 3; j -= 4) {
+ yy[j] = yy[j - 1];
+ yy[j - 1] = yy[j - 2];
+ yy[j - 2] = yy[j - 3];
+ yy[j - 3] = yy[j - 4];
+ }
+ for (; j > 0; j--) {
+ yy[j] = yy[j - 1];
+ }
+
+ yy[0] = tmp;
+ {
+ int zt, zn, zvec, zj;
+ if (groupPos == 0) {
+ groupNo++;
+ groupPos = G_SIZE;
+ }
+ groupPos--;
+ zt = selector[groupNo];
+ zn = minLens[zt];
+ zvec = bsR(zn);
+ while (zvec > limit[zt][zn]) {
+ zn++;
+ {
+ {
+ while (bsLive < 1) {
+ int zzi;
+ char thech = 0;
+ try {
+ thech = (char) bsStream.read();
+ } catch (IOException e) {
+ compressedStreamEOF();
+ }
+ zzi = thech;
+ bsBuff = (bsBuff << 8) | (zzi & 0xff);
+ bsLive += 8;
+ }
+ }
+ zj = (bsBuff >> (bsLive - 1)) & 1;
+ bsLive--;
+ }
+ zvec = (zvec << 1) | zj;
+ }
+ nextSym = perm[zt][zvec - base[zt][zn]];
+ }
+ continue;
+ }
+ }
+ }
+
+ private void setupBlock() {
+ int[] cftab = new int[257];
+ char ch;
+
+ cftab[0] = 0;
+ for (i = 1; i <= 256; i++) {
+ cftab[i] = unzftab[i - 1];
+ }
+ for (i = 1; i <= 256; i++) {
+ cftab[i] += cftab[i - 1];
+ }
+
+ for (i = 0; i <= last; i++) {
+ ch = (char) ll8[i];
+ tt[cftab[ch]] = i;
+ cftab[ch]++;
+ }
+ cftab = null;
+
+ tPos = tt[origPtr];
+
+ count = 0;
+ i2 = 0;
+ ch2 = 256; /* not a char and not EOF */
+
+ if (blockRandomised) {
+ rNToGo = 0;
+ rTPos = 0;
+ setupRandPartA();
+ } else {
+ setupNoRandPartA();
+ }
+ }
+
+ private void setupRandPartA() {
+ if (i2 <= last) {
+ chPrev = ch2;
+ ch2 = ll8[tPos];
+ tPos = tt[tPos];
+ if (rNToGo == 0) {
+ rNToGo = rNums[rTPos];
+ rTPos++;
+ if (rTPos == 512) {
+ rTPos = 0;
+ }
+ }
+ rNToGo--;
+ ch2 ^= (int) ((rNToGo == 1) ? 1 : 0);
+ i2++;
+
+ currentChar = ch2;
+ currentState = RAND_PART_B_STATE;
+ mCrc.updateCRC(ch2);
+ } else {
+ endBlock();
+ initBlock();
+ setupBlock();
+ }
+ }
+
+ private void setupNoRandPartA() {
+ if (i2 <= last) {
+ chPrev = ch2;
+ ch2 = ll8[tPos];
+ tPos = tt[tPos];
+ i2++;
+
+ currentChar = ch2;
+ currentState = NO_RAND_PART_B_STATE;
+ mCrc.updateCRC(ch2);
+ } else {
+ endBlock();
+ initBlock();
+ setupBlock();
+ }
+ }
+
+ private void setupRandPartB() {
+ if (ch2 != chPrev) {
+ currentState = RAND_PART_A_STATE;
+ count = 1;
+ setupRandPartA();
+ } else {
+ count++;
+ if (count >= 4) {
+ z = ll8[tPos];
+ tPos = tt[tPos];
+ if (rNToGo == 0) {
+ rNToGo = rNums[rTPos];
+ rTPos++;
+ if (rTPos == 512) {
+ rTPos = 0;
+ }
+ }
+ rNToGo--;
+ z ^= ((rNToGo == 1) ? 1 : 0);
+ j2 = 0;
+ currentState = RAND_PART_C_STATE;
+ setupRandPartC();
+ } else {
+ currentState = RAND_PART_A_STATE;
+ setupRandPartA();
+ }
+ }
+ }
+
+ private void setupRandPartC() {
+ if (j2 < (int) z) {
+ currentChar = ch2;
+ mCrc.updateCRC(ch2);
+ j2++;
+ } else {
+ currentState = RAND_PART_A_STATE;
+ i2++;
+ count = 0;
+ setupRandPartA();
+ }
+ }
+
+ private void setupNoRandPartB() {
+ if (ch2 != chPrev) {
+ currentState = NO_RAND_PART_A_STATE;
+ count = 1;
+ setupNoRandPartA();
+ } else {
+ count++;
+ if (count >= 4) {
+ z = ll8[tPos];
+ tPos = tt[tPos];
+ currentState = NO_RAND_PART_C_STATE;
+ j2 = 0;
+ setupNoRandPartC();
+ } else {
+ currentState = NO_RAND_PART_A_STATE;
+ setupNoRandPartA();
+ }
+ }
+ }
+
+ private void setupNoRandPartC() {
+ if (j2 < (int) z) {
+ currentChar = ch2;
+ mCrc.updateCRC(ch2);
+ j2++;
+ } else {
+ currentState = NO_RAND_PART_A_STATE;
+ i2++;
+ count = 0;
+ setupNoRandPartA();
+ }
+ }
+
+ private void setDecompressStructureSizes(int newSize100k) {
+ if (!(0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k
+ && blockSize100k <= 9)) {
+ // throw new IOException("Invalid block size");
+ }
+
+ blockSize100k = newSize100k;
+
+ if (newSize100k == 0) {
+ return;
+ }
+
+ int n = baseBlockSize * newSize100k;
+ ll8 = new char[n];
+ tt = new int[n];
+ }
+}
+
diff --git a/pg/src/main/java/org/bouncycastle/apache/bzip2/CBZip2OutputStream.java b/pg/src/main/java/org/bouncycastle/apache/bzip2/CBZip2OutputStream.java
new file mode 100644
index 00000000..0503583a
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/apache/bzip2/CBZip2OutputStream.java
@@ -0,0 +1,1651 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ *
+ */
+
+/*
+ * This package is based on the work done by Keiron Liddle, Aftex Software
+ * <keiron@aftexsw.com> to whom the Ant project is very grateful for his
+ * great code.
+ */
+
+package org.bouncycastle.apache.bzip2;
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+/**
+ * An output stream that compresses into the BZip2 format (with the file
+ * header chars) into another stream.
+ *
+ * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a>
+ *
+ * TODO: Update to BZip2 1.0.1
+ * <b>NB:</b> note this class has been modified to add a leading BZ to the
+ * start of the BZIP2 stream to make it compatible with other PGP programs.
+ */
+public class CBZip2OutputStream extends OutputStream implements BZip2Constants {
+ protected static final int SETMASK = (1 << 21);
+ protected static final int CLEARMASK = (~SETMASK);
+ protected static final int GREATER_ICOST = 15;
+ protected static final int LESSER_ICOST = 0;
+ protected static final int SMALL_THRESH = 20;
+ protected static final int DEPTH_THRESH = 10;
+
+ /*
+ If you are ever unlucky/improbable enough
+ to get a stack overflow whilst sorting,
+ increase the following constant and try
+ again. In practice I have never seen the
+ stack go above 27 elems, so the following
+ limit seems very generous.
+ */
+ protected static final int QSORT_STACK_SIZE = 1000;
+ private boolean finished;
+
+ private static void panic() {
+ System.out.println("panic");
+ //throw new CError();
+ }
+
+ private void makeMaps() {
+ int i;
+ nInUse = 0;
+ for (i = 0; i < 256; i++) {
+ if (inUse[i]) {
+ seqToUnseq[nInUse] = (char) i;
+ unseqToSeq[i] = (char) nInUse;
+ nInUse++;
+ }
+ }
+ }
+
+ protected static void hbMakeCodeLengths(char[] len, int[] freq,
+ int alphaSize, int maxLen) {
+ /*
+ Nodes and heap entries run from 1. Entry 0
+ for both the heap and nodes is a sentinel.
+ */
+ int nNodes, nHeap, n1, n2, i, j, k;
+ boolean tooLong;
+
+ int[] heap = new int[MAX_ALPHA_SIZE + 2];
+ int[] weight = new int[MAX_ALPHA_SIZE * 2];
+ int[] parent = new int[MAX_ALPHA_SIZE * 2];
+
+ for (i = 0; i < alphaSize; i++) {
+ weight[i + 1] = (freq[i] == 0 ? 1 : freq[i]) << 8;
+ }
+
+ while (true) {
+ nNodes = alphaSize;
+ nHeap = 0;
+
+ heap[0] = 0;
+ weight[0] = 0;
+ parent[0] = -2;
+
+ for (i = 1; i <= alphaSize; i++) {
+ parent[i] = -1;
+ nHeap++;
+ heap[nHeap] = i;
+ {
+ int zz, tmp;
+ zz = nHeap;
+ tmp = heap[zz];
+ while (weight[tmp] < weight[heap[zz >> 1]]) {
+ heap[zz] = heap[zz >> 1];
+ zz >>= 1;
+ }
+ heap[zz] = tmp;
+ }
+ }
+ if (!(nHeap < (MAX_ALPHA_SIZE + 2))) {
+ panic();
+ }
+
+ while (nHeap > 1) {
+ n1 = heap[1];
+ heap[1] = heap[nHeap];
+ nHeap--;
+ {
+ int zz = 0, yy = 0, tmp = 0;
+ zz = 1;
+ tmp = heap[zz];
+ while (true) {
+ yy = zz << 1;
+ if (yy > nHeap) {
+ break;
+ }
+ if (yy < nHeap
+ && weight[heap[yy + 1]] < weight[heap[yy]]) {
+ yy++;
+ }
+ if (weight[tmp] < weight[heap[yy]]) {
+ break;
+ }
+ heap[zz] = heap[yy];
+ zz = yy;
+ }
+ heap[zz] = tmp;
+ }
+ n2 = heap[1];
+ heap[1] = heap[nHeap];
+ nHeap--;
+ {
+ int zz = 0, yy = 0, tmp = 0;
+ zz = 1;
+ tmp = heap[zz];
+ while (true) {
+ yy = zz << 1;
+ if (yy > nHeap) {
+ break;
+ }
+ if (yy < nHeap
+ && weight[heap[yy + 1]] < weight[heap[yy]]) {
+ yy++;
+ }
+ if (weight[tmp] < weight[heap[yy]]) {
+ break;
+ }
+ heap[zz] = heap[yy];
+ zz = yy;
+ }
+ heap[zz] = tmp;
+ }
+ nNodes++;
+ parent[n1] = parent[n2] = nNodes;
+
+ weight[nNodes] = ((weight[n1] & 0xffffff00)
+ + (weight[n2] & 0xffffff00))
+ | (1 + (((weight[n1] & 0x000000ff) >
+ (weight[n2] & 0x000000ff)) ?
+ (weight[n1] & 0x000000ff) :
+ (weight[n2] & 0x000000ff)));
+
+ parent[nNodes] = -1;
+ nHeap++;
+ heap[nHeap] = nNodes;
+ {
+ int zz = 0, tmp = 0;
+ zz = nHeap;
+ tmp = heap[zz];
+ while (weight[tmp] < weight[heap[zz >> 1]]) {
+ heap[zz] = heap[zz >> 1];
+ zz >>= 1;
+ }
+ heap[zz] = tmp;
+ }
+ }
+ if (!(nNodes < (MAX_ALPHA_SIZE * 2))) {
+ panic();
+ }
+
+ tooLong = false;
+ for (i = 1; i <= alphaSize; i++) {
+ j = 0;
+ k = i;
+ while (parent[k] >= 0) {
+ k = parent[k];
+ j++;
+ }
+ len[i - 1] = (char) j;
+ if (j > maxLen) {
+ tooLong = true;
+ }
+ }
+
+ if (!tooLong) {
+ break;
+ }
+
+ for (i = 1; i < alphaSize; i++) {
+ j = weight[i] >> 8;
+ j = 1 + (j / 2);
+ weight[i] = j << 8;
+ }
+ }
+ }
+
+ /*
+ index of the last char in the block, so
+ the block size == last + 1.
+ */
+ int last;
+
+ /*
+ index in zptr[] of original string after sorting.
+ */
+ int origPtr;
+
+ /*
+ always: in the range 0 .. 9.
+ The current block size is 100000 * this number.
+ */
+ int blockSize100k;
+
+ boolean blockRandomised;
+
+ int bytesOut;
+ int bsBuff;
+ int bsLive;
+ CRC mCrc = new CRC();
+
+ private boolean[] inUse = new boolean[256];
+ private int nInUse;
+
+ private char[] seqToUnseq = new char[256];
+ private char[] unseqToSeq = new char[256];
+
+ private char[] selector = new char[MAX_SELECTORS];
+ private char[] selectorMtf = new char[MAX_SELECTORS];
+
+ private char[] block;
+ private int[] quadrant;
+ private int[] zptr;
+ private short[] szptr;
+ private int[] ftab;
+
+ private int nMTF;
+
+ private int[] mtfFreq = new int[MAX_ALPHA_SIZE];
+
+ /*
+ * Used when sorting. If too many long comparisons
+ * happen, we stop sorting, randomise the block
+ * slightly, and try again.
+ */
+ private int workFactor;
+ private int workDone;
+ private int workLimit;
+ private boolean firstAttempt;
+ private int nBlocksRandomised;
+
+ private int currentChar = -1;
+ private int runLength = 0;
+
+ public CBZip2OutputStream(OutputStream inStream) throws IOException {
+ this(inStream, 9);
+ }
+
+ public CBZip2OutputStream(OutputStream inStream, int inBlockSize)
+ throws IOException {
+ block = null;
+ quadrant = null;
+ zptr = null;
+ ftab = null;
+
+ inStream.write('B');
+ inStream.write('Z');
+
+ bsSetStream(inStream);
+
+ workFactor = 50;
+ if (inBlockSize > 9) {
+ inBlockSize = 9;
+ }
+ if (inBlockSize < 1) {
+ inBlockSize = 1;
+ }
+ blockSize100k = inBlockSize;
+ allocateCompressStructures();
+ initialize();
+ initBlock();
+ }
+
+ /**
+ *
+ * modified by Oliver Merkel, 010128
+ *
+ */
+ public void write(int bv) throws IOException {
+ int b = (256 + bv) % 256;
+ if (currentChar != -1) {
+ if (currentChar == b) {
+ runLength++;
+ if (runLength > 254) {
+ writeRun();
+ currentChar = -1;
+ runLength = 0;
+ }
+ } else {
+ writeRun();
+ runLength = 1;
+ currentChar = b;
+ }
+ } else {
+ currentChar = b;
+ runLength++;
+ }
+ }
+
+ private void writeRun() throws IOException {
+ if (last < allowableBlockSize) {
+ inUse[currentChar] = true;
+ for (int i = 0; i < runLength; i++) {
+ mCrc.updateCRC((char) currentChar);
+ }
+ switch (runLength) {
+ case 1:
+ last++;
+ block[last + 1] = (char) currentChar;
+ break;
+ case 2:
+ last++;
+ block[last + 1] = (char) currentChar;
+ last++;
+ block[last + 1] = (char) currentChar;
+ break;
+ case 3:
+ last++;
+ block[last + 1] = (char) currentChar;
+ last++;
+ block[last + 1] = (char) currentChar;
+ last++;
+ block[last + 1] = (char) currentChar;
+ break;
+ default:
+ inUse[runLength - 4] = true;
+ last++;
+ block[last + 1] = (char) currentChar;
+ last++;
+ block[last + 1] = (char) currentChar;
+ last++;
+ block[last + 1] = (char) currentChar;
+ last++;
+ block[last + 1] = (char) currentChar;
+ last++;
+ block[last + 1] = (char) (runLength - 4);
+ break;
+ }
+ } else {
+ endBlock();
+ initBlock();
+ writeRun();
+ }
+ }
+
+ boolean closed = false;
+
+ protected void finalize() throws Throwable {
+ close();
+ super.finalize();
+ }
+
+ public void close() throws IOException {
+ if (closed) {
+ return;
+ }
+
+ finish();
+
+ closed = true;
+ super.close();
+ bsStream.close();
+ }
+
+ public void finish() throws IOException {
+ if (finished) {
+ return;
+ }
+
+ if (runLength > 0) {
+ writeRun();
+ }
+ currentChar = -1;
+ endBlock();
+ endCompression();
+ finished = true;
+ flush();
+ }
+
+ public void flush() throws IOException {
+ super.flush();
+ bsStream.flush();
+ }
+
+ private int blockCRC, combinedCRC;
+
+ private void initialize() throws IOException {
+ bytesOut = 0;
+ nBlocksRandomised = 0;
+
+ /* Write `magic' bytes h indicating file-format == huffmanised,
+ followed by a digit indicating blockSize100k.
+ */
+ bsPutUChar('h');
+ bsPutUChar('0' + blockSize100k);
+
+ combinedCRC = 0;
+ }
+
+ private int allowableBlockSize;
+
+ private void initBlock() {
+ // blockNo++;
+ mCrc.initialiseCRC();
+ last = -1;
+ // ch = 0;
+
+ for (int i = 0; i < 256; i++) {
+ inUse[i] = false;
+ }
+
+ /* 20 is just a paranoia constant */
+ allowableBlockSize = baseBlockSize * blockSize100k - 20;
+ }
+
+ private void endBlock() throws IOException {
+ blockCRC = mCrc.getFinalCRC();
+ combinedCRC = (combinedCRC << 1) | (combinedCRC >>> 31);
+ combinedCRC ^= blockCRC;
+
+ /* sort the block and establish posn of original string */
+ doReversibleTransformation();
+
+ /*
+ A 6-byte block header, the value chosen arbitrarily
+ as 0x314159265359 :-). A 32 bit value does not really
+ give a strong enough guarantee that the value will not
+ appear by chance in the compressed datastream. Worst-case
+ probability of this event, for a 900k block, is about
+ 2.0e-3 for 32 bits, 1.0e-5 for 40 bits and 4.0e-8 for 48 bits.
+ For a compressed file of size 100Gb -- about 100000 blocks --
+ only a 48-bit marker will do. NB: normal compression/
+ decompression do *not* rely on these statistical properties.
+ They are only important when trying to recover blocks from
+ damaged files.
+ */
+ bsPutUChar(0x31);
+ bsPutUChar(0x41);
+ bsPutUChar(0x59);
+ bsPutUChar(0x26);
+ bsPutUChar(0x53);
+ bsPutUChar(0x59);
+
+ /* Now the block's CRC, so it is in a known place. */
+ bsPutint(blockCRC);
+
+ /* Now a single bit indicating randomisation. */
+ if (blockRandomised) {
+ bsW(1, 1);
+ nBlocksRandomised++;
+ } else {
+ bsW(1, 0);
+ }
+
+ /* Finally, block's contents proper. */
+ moveToFrontCodeAndSend();
+ }
+
+ private void endCompression() throws IOException {
+ /*
+ Now another magic 48-bit number, 0x177245385090, to
+ indicate the end of the last block. (sqrt(pi), if
+ you want to know. I did want to use e, but it contains
+ too much repetition -- 27 18 28 18 28 46 -- for me
+ to feel statistically comfortable. Call me paranoid.)
+ */
+ bsPutUChar(0x17);
+ bsPutUChar(0x72);
+ bsPutUChar(0x45);
+ bsPutUChar(0x38);
+ bsPutUChar(0x50);
+ bsPutUChar(0x90);
+
+ bsPutint(combinedCRC);
+
+ bsFinishedWithStream();
+ }
+
+ private void hbAssignCodes (int[] code, char[] length, int minLen,
+ int maxLen, int alphaSize) {
+ int n, vec, i;
+
+ vec = 0;
+ for (n = minLen; n <= maxLen; n++) {
+ for (i = 0; i < alphaSize; i++) {
+ if (length[i] == n) {
+ code[i] = vec;
+ vec++;
+ }
+ }
+ vec <<= 1;
+ }
+ }
+
+ private void bsSetStream(OutputStream f) {
+ bsStream = f;
+ bsLive = 0;
+ bsBuff = 0;
+ bytesOut = 0;
+ }
+
+ private void bsFinishedWithStream() throws IOException {
+ while (bsLive > 0) {
+ int ch = (bsBuff >> 24);
+ try {
+ bsStream.write(ch); // write 8-bit
+ } catch (IOException e) {
+ throw e;
+ }
+ bsBuff <<= 8;
+ bsLive -= 8;
+ bytesOut++;
+ }
+ }
+
+ private void bsW(int n, int v) throws IOException {
+ while (bsLive >= 8) {
+ int ch = (bsBuff >> 24);
+ try {
+ bsStream.write(ch); // write 8-bit
+ } catch (IOException e) {
+ throw e;
+ }
+ bsBuff <<= 8;
+ bsLive -= 8;
+ bytesOut++;
+ }
+ bsBuff |= (v << (32 - bsLive - n));
+ bsLive += n;
+ }
+
+ private void bsPutUChar(int c) throws IOException {
+ bsW(8, c);
+ }
+
+ private void bsPutint(int u) throws IOException {
+ bsW(8, (u >> 24) & 0xff);
+ bsW(8, (u >> 16) & 0xff);
+ bsW(8, (u >> 8) & 0xff);
+ bsW(8, u & 0xff);
+ }
+
+ private void bsPutIntVS(int numBits, int c) throws IOException {
+ bsW(numBits, c);
+ }
+
+ private void sendMTFValues() throws IOException {
+ char len[][] = new char[N_GROUPS][MAX_ALPHA_SIZE];
+
+ int v, t, i, j, gs, ge, totc, bt, bc, iter;
+ int nSelectors = 0, alphaSize, minLen, maxLen, selCtr;
+ int nGroups;//, nBytes;
+
+ alphaSize = nInUse + 2;
+ for (t = 0; t < N_GROUPS; t++) {
+ for (v = 0; v < alphaSize; v++) {
+ len[t][v] = (char) GREATER_ICOST;
+ }
+ }
+
+ /* Decide how many coding tables to use */
+ if (nMTF <= 0) {
+ panic();
+ }
+
+ if (nMTF < 200) {
+ nGroups = 2;
+ } else if (nMTF < 600) {
+ nGroups = 3;
+ } else if (nMTF < 1200) {
+ nGroups = 4;
+ } else if (nMTF < 2400) {
+ nGroups = 5;
+ } else {
+ nGroups = 6;
+ }
+
+ /* Generate an initial set of coding tables */ {
+ int nPart, remF, tFreq, aFreq;
+
+ nPart = nGroups;
+ remF = nMTF;
+ gs = 0;
+ while (nPart > 0) {
+ tFreq = remF / nPart;
+ ge = gs - 1;
+ aFreq = 0;
+ while (aFreq < tFreq && ge < alphaSize - 1) {
+ ge++;
+ aFreq += mtfFreq[ge];
+ }
+
+ if (ge > gs && nPart != nGroups && nPart != 1
+ && ((nGroups - nPart) % 2 == 1)) {
+ aFreq -= mtfFreq[ge];
+ ge--;
+ }
+
+ for (v = 0; v < alphaSize; v++) {
+ if (v >= gs && v <= ge) {
+ len[nPart - 1][v] = (char) LESSER_ICOST;
+ } else {
+ len[nPart - 1][v] = (char) GREATER_ICOST;
+ }
+ }
+
+ nPart--;
+ gs = ge + 1;
+ remF -= aFreq;
+ }
+ }
+
+ int[][] rfreq = new int[N_GROUPS][MAX_ALPHA_SIZE];
+ int[] fave = new int[N_GROUPS];
+ short[] cost = new short[N_GROUPS];
+ /*
+ Iterate up to N_ITERS times to improve the tables.
+ */
+ for (iter = 0; iter < N_ITERS; iter++) {
+ for (t = 0; t < nGroups; t++) {
+ fave[t] = 0;
+ }
+
+ for (t = 0; t < nGroups; t++) {
+ for (v = 0; v < alphaSize; v++) {
+ rfreq[t][v] = 0;
+ }
+ }
+
+ nSelectors = 0;
+ totc = 0;
+ gs = 0;
+ while (true) {
+
+ /* Set group start & end marks. */
+ if (gs >= nMTF) {
+ break;
+ }
+ ge = gs + G_SIZE - 1;
+ if (ge >= nMTF) {
+ ge = nMTF - 1;
+ }
+
+ /*
+ Calculate the cost of this group as coded
+ by each of the coding tables.
+ */
+ for (t = 0; t < nGroups; t++) {
+ cost[t] = 0;
+ }
+
+ if (nGroups == 6) {
+ short cost0, cost1, cost2, cost3, cost4, cost5;
+ cost0 = cost1 = cost2 = cost3 = cost4 = cost5 = 0;
+ for (i = gs; i <= ge; i++) {
+ short icv = szptr[i];
+ cost0 += len[0][icv];
+ cost1 += len[1][icv];
+ cost2 += len[2][icv];
+ cost3 += len[3][icv];
+ cost4 += len[4][icv];
+ cost5 += len[5][icv];
+ }
+ cost[0] = cost0;
+ cost[1] = cost1;
+ cost[2] = cost2;
+ cost[3] = cost3;
+ cost[4] = cost4;
+ cost[5] = cost5;
+ } else {
+ for (i = gs; i <= ge; i++) {
+ short icv = szptr[i];
+ for (t = 0; t < nGroups; t++) {
+ cost[t] += len[t][icv];
+ }
+ }
+ }
+
+ /*
+ Find the coding table which is best for this group,
+ and record its identity in the selector table.
+ */
+ bc = 999999999;
+ bt = -1;
+ for (t = 0; t < nGroups; t++) {
+ if (cost[t] < bc) {
+ bc = cost[t];
+ bt = t;
+ }
+ }
+ totc += bc;
+ fave[bt]++;
+ selector[nSelectors] = (char) bt;
+ nSelectors++;
+
+ /*
+ Increment the symbol frequencies for the selected table.
+ */
+ for (i = gs; i <= ge; i++) {
+ rfreq[bt][szptr[i]]++;
+ }
+
+ gs = ge + 1;
+ }
+
+ /*
+ Recompute the tables based on the accumulated frequencies.
+ */
+ for (t = 0; t < nGroups; t++) {
+ hbMakeCodeLengths(len[t], rfreq[t], alphaSize, 20);
+ }
+ }
+
+ rfreq = null;
+ fave = null;
+ cost = null;
+
+ if (!(nGroups < 8)) {
+ panic();
+ }
+ if (!(nSelectors < 32768 && nSelectors <= (2 + (900000 / G_SIZE)))) {
+ panic();
+ }
+
+
+ /* Compute MTF values for the selectors. */
+ {
+ char[] pos = new char[N_GROUPS];
+ char ll_i, tmp2, tmp;
+ for (i = 0; i < nGroups; i++) {
+ pos[i] = (char) i;
+ }
+ for (i = 0; i < nSelectors; i++) {
+ ll_i = selector[i];
+ j = 0;
+ tmp = pos[j];
+ while (ll_i != tmp) {
+ j++;
+ tmp2 = tmp;
+ tmp = pos[j];
+ pos[j] = tmp2;
+ }
+ pos[0] = tmp;
+ selectorMtf[i] = (char) j;
+ }
+ }
+
+ int[][] code = new int[N_GROUPS][MAX_ALPHA_SIZE];
+
+ /* Assign actual codes for the tables. */
+ for (t = 0; t < nGroups; t++) {
+ minLen = 32;
+ maxLen = 0;
+ for (i = 0; i < alphaSize; i++) {
+ if (len[t][i] > maxLen) {
+ maxLen = len[t][i];
+ }
+ if (len[t][i] < minLen) {
+ minLen = len[t][i];
+ }
+ }
+ if (maxLen > 20) {
+ panic();
+ }
+ if (minLen < 1) {
+ panic();
+ }
+ hbAssignCodes(code[t], len[t], minLen, maxLen, alphaSize);
+ }
+
+ /* Transmit the mapping table. */
+ {
+ boolean[] inUse16 = new boolean[16];
+ for (i = 0; i < 16; i++) {
+ inUse16[i] = false;
+ for (j = 0; j < 16; j++) {
+ if (inUse[i * 16 + j]) {
+ inUse16[i] = true;
+ }
+ }
+ }
+
+// nBytes = bytesOut;
+ for (i = 0; i < 16; i++) {
+ if (inUse16[i]) {
+ bsW(1, 1);
+ } else {
+ bsW(1, 0);
+ }
+ }
+
+ for (i = 0; i < 16; i++) {
+ if (inUse16[i]) {
+ for (j = 0; j < 16; j++) {
+ if (inUse[i * 16 + j]) {
+ bsW(1, 1);
+ } else {
+ bsW(1, 0);
+ }
+ }
+ }
+ }
+
+ }
+
+ /* Now the selectors. */
+// nBytes = bytesOut;
+ bsW (3, nGroups);
+ bsW (15, nSelectors);
+ for (i = 0; i < nSelectors; i++) {
+ for (j = 0; j < selectorMtf[i]; j++) {
+ bsW(1, 1);
+ }
+ bsW(1, 0);
+ }
+
+ /* Now the coding tables. */
+// nBytes = bytesOut;
+
+ for (t = 0; t < nGroups; t++) {
+ int curr = len[t][0];
+ bsW(5, curr);
+ for (i = 0; i < alphaSize; i++) {
+ while (curr < len[t][i]) {
+ bsW(2, 2);
+ curr++; /* 10 */
+ }
+ while (curr > len[t][i]) {
+ bsW(2, 3);
+ curr--; /* 11 */
+ }
+ bsW (1, 0);
+ }
+ }
+
+ /* And finally, the block data proper */
+// nBytes = bytesOut;
+ selCtr = 0;
+ gs = 0;
+ while (true) {
+ if (gs >= nMTF) {
+ break;
+ }
+ ge = gs + G_SIZE - 1;
+ if (ge >= nMTF) {
+ ge = nMTF - 1;
+ }
+ for (i = gs; i <= ge; i++) {
+ bsW(len[selector[selCtr]][szptr[i]],
+ code[selector[selCtr]][szptr[i]]);
+ }
+
+ gs = ge + 1;
+ selCtr++;
+ }
+ if (!(selCtr == nSelectors)) {
+ panic();
+ }
+ }
+
+ private void moveToFrontCodeAndSend () throws IOException {
+ bsPutIntVS(24, origPtr);
+ generateMTFValues();
+ sendMTFValues();
+ }
+
+ private OutputStream bsStream;
+
+ private void simpleSort(int lo, int hi, int d) {
+ int i, j, h, bigN, hp;
+ int v;
+
+ bigN = hi - lo + 1;
+ if (bigN < 2) {
+ return;
+ }
+
+ hp = 0;
+ while (incs[hp] < bigN) {
+ hp++;
+ }
+ hp--;
+
+ for (; hp >= 0; hp--) {
+ h = incs[hp];
+
+ i = lo + h;
+ while (true) {
+ /* copy 1 */
+ if (i > hi) {
+ break;
+ }
+ v = zptr[i];
+ j = i;
+ while (fullGtU(zptr[j - h] + d, v + d)) {
+ zptr[j] = zptr[j - h];
+ j = j - h;
+ if (j <= (lo + h - 1)) {
+ break;
+ }
+ }
+ zptr[j] = v;
+ i++;
+
+ /* copy 2 */
+ if (i > hi) {
+ break;
+ }
+ v = zptr[i];
+ j = i;
+ while (fullGtU(zptr[j - h] + d, v + d)) {
+ zptr[j] = zptr[j - h];
+ j = j - h;
+ if (j <= (lo + h - 1)) {
+ break;
+ }
+ }
+ zptr[j] = v;
+ i++;
+
+ /* copy 3 */
+ if (i > hi) {
+ break;
+ }
+ v = zptr[i];
+ j = i;
+ while (fullGtU(zptr[j - h] + d, v + d)) {
+ zptr[j] = zptr[j - h];
+ j = j - h;
+ if (j <= (lo + h - 1)) {
+ break;
+ }
+ }
+ zptr[j] = v;
+ i++;
+
+ if (workDone > workLimit && firstAttempt) {
+ return;
+ }
+ }
+ }
+ }
+
+ private void vswap(int p1, int p2, int n) {
+ int temp = 0;
+ while (n > 0) {
+ temp = zptr[p1];
+ zptr[p1] = zptr[p2];
+ zptr[p2] = temp;
+ p1++;
+ p2++;
+ n--;
+ }
+ }
+
+ private char med3(char a, char b, char c) {
+ char t;
+ if (a > b) {
+ t = a;
+ a = b;
+ b = t;
+ }
+ if (b > c) {
+ t = b;
+ b = c;
+ c = t;
+ }
+ if (a > b) {
+ b = a;
+ }
+ return b;
+ }
+
+ private static class StackElem {
+ int ll;
+ int hh;
+ int dd;
+ }
+
+ private void qSort3(int loSt, int hiSt, int dSt) {
+ int unLo, unHi, ltLo, gtHi, med, n, m;
+ int sp, lo, hi, d;
+ StackElem[] stack = new StackElem[QSORT_STACK_SIZE];
+ for (int count = 0; count < QSORT_STACK_SIZE; count++) {
+ stack[count] = new StackElem();
+ }
+
+ sp = 0;
+
+ stack[sp].ll = loSt;
+ stack[sp].hh = hiSt;
+ stack[sp].dd = dSt;
+ sp++;
+
+ while (sp > 0) {
+ if (sp >= QSORT_STACK_SIZE) {
+ panic();
+ }
+
+ sp--;
+ lo = stack[sp].ll;
+ hi = stack[sp].hh;
+ d = stack[sp].dd;
+
+ if (hi - lo < SMALL_THRESH || d > DEPTH_THRESH) {
+ simpleSort(lo, hi, d);
+ if (workDone > workLimit && firstAttempt) {
+ return;
+ }
+ continue;
+ }
+
+ med = med3(block[zptr[lo] + d + 1],
+ block[zptr[hi ] + d + 1],
+ block[zptr[(lo + hi) >> 1] + d + 1]);
+
+ unLo = ltLo = lo;
+ unHi = gtHi = hi;
+
+ while (true) {
+ while (true) {
+ if (unLo > unHi) {
+ break;
+ }
+ n = ((int) block[zptr[unLo] + d + 1]) - med;
+ if (n == 0) {
+ int temp = 0;
+ temp = zptr[unLo];
+ zptr[unLo] = zptr[ltLo];
+ zptr[ltLo] = temp;
+ ltLo++;
+ unLo++;
+ continue;
+ }
+ if (n > 0) {
+ break;
+ }
+ unLo++;
+ }
+ while (true) {
+ if (unLo > unHi) {
+ break;
+ }
+ n = ((int) block[zptr[unHi] + d + 1]) - med;
+ if (n == 0) {
+ int temp = 0;
+ temp = zptr[unHi];
+ zptr[unHi] = zptr[gtHi];
+ zptr[gtHi] = temp;
+ gtHi--;
+ unHi--;
+ continue;
+ }
+ if (n < 0) {
+ break;
+ }
+ unHi--;
+ }
+ if (unLo > unHi) {
+ break;
+ }
+ int temp = 0;
+ temp = zptr[unLo];
+ zptr[unLo] = zptr[unHi];
+ zptr[unHi] = temp;
+ unLo++;
+ unHi--;
+ }
+
+ if (gtHi < ltLo) {
+ stack[sp].ll = lo;
+ stack[sp].hh = hi;
+ stack[sp].dd = d + 1;
+ sp++;
+ continue;
+ }
+
+ n = ((ltLo - lo) < (unLo - ltLo)) ? (ltLo - lo) : (unLo - ltLo);
+ vswap(lo, unLo - n, n);
+ m = ((hi - gtHi) < (gtHi - unHi)) ? (hi - gtHi) : (gtHi - unHi);
+ vswap(unLo, hi - m + 1, m);
+
+ n = lo + unLo - ltLo - 1;
+ m = hi - (gtHi - unHi) + 1;
+
+ stack[sp].ll = lo;
+ stack[sp].hh = n;
+ stack[sp].dd = d;
+ sp++;
+
+ stack[sp].ll = n + 1;
+ stack[sp].hh = m - 1;
+ stack[sp].dd = d + 1;
+ sp++;
+
+ stack[sp].ll = m;
+ stack[sp].hh = hi;
+ stack[sp].dd = d;
+ sp++;
+ }
+ }
+
+ private void mainSort() {
+ int i, j, ss, sb;
+ int[] runningOrder = new int[256];
+ int[] copy = new int[256];
+ boolean[] bigDone = new boolean[256];
+ int c1, c2;
+ int numQSorted;
+
+ /*
+ In the various block-sized structures, live data runs
+ from 0 to last+NUM_OVERSHOOT_BYTES inclusive. First,
+ set up the overshoot area for block.
+ */
+
+ // if (verbosity >= 4) fprintf ( stderr, " sort initialise ...\n" );
+ for (i = 0; i < NUM_OVERSHOOT_BYTES; i++) {
+ block[last + i + 2] = block[(i % (last + 1)) + 1];
+ }
+ for (i = 0; i <= last + NUM_OVERSHOOT_BYTES; i++) {
+ quadrant[i] = 0;
+ }
+
+ block[0] = (char) (block[last + 1]);
+
+ if (last < 4000) {
+ /*
+ Use simpleSort(), since the full sorting mechanism
+ has quite a large constant overhead.
+ */
+ for (i = 0; i <= last; i++) {
+ zptr[i] = i;
+ }
+ firstAttempt = false;
+ workDone = workLimit = 0;
+ simpleSort(0, last, 0);
+ } else {
+ numQSorted = 0;
+ for (i = 0; i <= 255; i++) {
+ bigDone[i] = false;
+ }
+
+ for (i = 0; i <= 65536; i++) {
+ ftab[i] = 0;
+ }
+
+ c1 = block[0];
+ for (i = 0; i <= last; i++) {
+ c2 = block[i + 1];
+ ftab[(c1 << 8) + c2]++;
+ c1 = c2;
+ }
+
+ for (i = 1; i <= 65536; i++) {
+ ftab[i] += ftab[i - 1];
+ }
+
+ c1 = block[1];
+ for (i = 0; i < last; i++) {
+ c2 = block[i + 2];
+ j = (c1 << 8) + c2;
+ c1 = c2;
+ ftab[j]--;
+ zptr[ftab[j]] = i;
+ }
+
+ j = ((block[last + 1]) << 8) + (block[1]);
+ ftab[j]--;
+ zptr[ftab[j]] = last;
+
+ /*
+ Now ftab contains the first loc of every small bucket.
+ Calculate the running order, from smallest to largest
+ big bucket.
+ */
+
+ for (i = 0; i <= 255; i++) {
+ runningOrder[i] = i;
+ }
+
+ {
+ int vv;
+ int h = 1;
+ do {
+ h = 3 * h + 1;
+ }
+ while (h <= 256);
+ do {
+ h = h / 3;
+ for (i = h; i <= 255; i++) {
+ vv = runningOrder[i];
+ j = i;
+ while ((ftab[((runningOrder[j - h]) + 1) << 8]
+ - ftab[(runningOrder[j - h]) << 8]) >
+ (ftab[((vv) + 1) << 8] - ftab[(vv) << 8])) {
+ runningOrder[j] = runningOrder[j - h];
+ j = j - h;
+ if (j <= (h - 1)) {
+ break;
+ }
+ }
+ runningOrder[j] = vv;
+ }
+ } while (h != 1);
+ }
+
+ /*
+ The main sorting loop.
+ */
+ for (i = 0; i <= 255; i++) {
+
+ /*
+ Process big buckets, starting with the least full.
+ */
+ ss = runningOrder[i];
+
+ /*
+ Complete the big bucket [ss] by quicksorting
+ any unsorted small buckets [ss, j]. Hopefully
+ previous pointer-scanning phases have already
+ completed many of the small buckets [ss, j], so
+ we don't have to sort them at all.
+ */
+ for (j = 0; j <= 255; j++) {
+ sb = (ss << 8) + j;
+ if (!((ftab[sb] & SETMASK) == SETMASK)) {
+ int lo = ftab[sb] & CLEARMASK;
+ int hi = (ftab[sb + 1] & CLEARMASK) - 1;
+ if (hi > lo) {
+ qSort3(lo, hi, 2);
+ numQSorted += (hi - lo + 1);
+ if (workDone > workLimit && firstAttempt) {
+ return;
+ }
+ }
+ ftab[sb] |= SETMASK;
+ }
+ }
+
+ /*
+ The ss big bucket is now done. Record this fact,
+ and update the quadrant descriptors. Remember to
+ update quadrants in the overshoot area too, if
+ necessary. The "if (i < 255)" test merely skips
+ this updating for the last bucket processed, since
+ updating for the last bucket is pointless.
+ */
+ bigDone[ss] = true;
+
+ if (i < 255) {
+ int bbStart = ftab[ss << 8] & CLEARMASK;
+ int bbSize = (ftab[(ss + 1) << 8] & CLEARMASK) - bbStart;
+ int shifts = 0;
+
+ while ((bbSize >> shifts) > 65534) {
+ shifts++;
+ }
+
+ for (j = 0; j < bbSize; j++) {
+ int a2update = zptr[bbStart + j];
+ int qVal = (j >> shifts);
+ quadrant[a2update] = qVal;
+ if (a2update < NUM_OVERSHOOT_BYTES) {
+ quadrant[a2update + last + 1] = qVal;
+ }
+ }
+
+ if (!(((bbSize - 1) >> shifts) <= 65535)) {
+ panic();
+ }
+ }
+
+ /*
+ Now scan this big bucket so as to synthesise the
+ sorted order for small buckets [t, ss] for all t != ss.
+ */
+ for (j = 0; j <= 255; j++) {
+ copy[j] = ftab[(j << 8) + ss] & CLEARMASK;
+ }
+
+ for (j = ftab[ss << 8] & CLEARMASK;
+ j < (ftab[(ss + 1) << 8] & CLEARMASK); j++) {
+ c1 = block[zptr[j]];
+ if (!bigDone[c1]) {
+ zptr[copy[c1]] = zptr[j] == 0 ? last : zptr[j] - 1;
+ copy[c1]++;
+ }
+ }
+
+ for (j = 0; j <= 255; j++) {
+ ftab[(j << 8) + ss] |= SETMASK;
+ }
+ }
+ }
+ }
+
+ private void randomiseBlock() {
+ int i;
+ int rNToGo = 0;
+ int rTPos = 0;
+ for (i = 0; i < 256; i++) {
+ inUse[i] = false;
+ }
+
+ for (i = 0; i <= last; i++) {
+ if (rNToGo == 0) {
+ rNToGo = (char) rNums[rTPos];
+ rTPos++;
+ if (rTPos == 512) {
+ rTPos = 0;
+ }
+ }
+ rNToGo--;
+ block[i + 1] ^= ((rNToGo == 1) ? 1 : 0);
+ // handle 16 bit signed numbers
+ block[i + 1] &= 0xFF;
+
+ inUse[block[i + 1]] = true;
+ }
+ }
+
+ private void doReversibleTransformation() {
+ int i;
+
+ workLimit = workFactor * last;
+ workDone = 0;
+ blockRandomised = false;
+ firstAttempt = true;
+
+ mainSort();
+
+ if (workDone > workLimit && firstAttempt) {
+ randomiseBlock();
+ workLimit = workDone = 0;
+ blockRandomised = true;
+ firstAttempt = false;
+ mainSort();
+ }
+
+ origPtr = -1;
+ for (i = 0; i <= last; i++) {
+ if (zptr[i] == 0) {
+ origPtr = i;
+ break;
+ }
+ }
+
+ if (origPtr == -1) {
+ panic();
+ }
+ }
+
+ private boolean fullGtU(int i1, int i2) {
+ int k;
+ char c1, c2;
+ int s1, s2;
+
+ c1 = block[i1 + 1];
+ c2 = block[i2 + 1];
+ if (c1 != c2) {
+ return (c1 > c2);
+ }
+ i1++;
+ i2++;
+
+ c1 = block[i1 + 1];
+ c2 = block[i2 + 1];
+ if (c1 != c2) {
+ return (c1 > c2);
+ }
+ i1++;
+ i2++;
+
+ c1 = block[i1 + 1];
+ c2 = block[i2 + 1];
+ if (c1 != c2) {
+ return (c1 > c2);
+ }
+ i1++;
+ i2++;
+
+ c1 = block[i1 + 1];
+ c2 = block[i2 + 1];
+ if (c1 != c2) {
+ return (c1 > c2);
+ }
+ i1++;
+ i2++;
+
+ c1 = block[i1 + 1];
+ c2 = block[i2 + 1];
+ if (c1 != c2) {
+ return (c1 > c2);
+ }
+ i1++;
+ i2++;
+
+ c1 = block[i1 + 1];
+ c2 = block[i2 + 1];
+ if (c1 != c2) {
+ return (c1 > c2);
+ }
+ i1++;
+ i2++;
+
+ k = last + 1;
+
+ do {
+ c1 = block[i1 + 1];
+ c2 = block[i2 + 1];
+ if (c1 != c2) {
+ return (c1 > c2);
+ }
+ s1 = quadrant[i1];
+ s2 = quadrant[i2];
+ if (s1 != s2) {
+ return (s1 > s2);
+ }
+ i1++;
+ i2++;
+
+ c1 = block[i1 + 1];
+ c2 = block[i2 + 1];
+ if (c1 != c2) {
+ return (c1 > c2);
+ }
+ s1 = quadrant[i1];
+ s2 = quadrant[i2];
+ if (s1 != s2) {
+ return (s1 > s2);
+ }
+ i1++;
+ i2++;
+
+ c1 = block[i1 + 1];
+ c2 = block[i2 + 1];
+ if (c1 != c2) {
+ return (c1 > c2);
+ }
+ s1 = quadrant[i1];
+ s2 = quadrant[i2];
+ if (s1 != s2) {
+ return (s1 > s2);
+ }
+ i1++;
+ i2++;
+
+ c1 = block[i1 + 1];
+ c2 = block[i2 + 1];
+ if (c1 != c2) {
+ return (c1 > c2);
+ }
+ s1 = quadrant[i1];
+ s2 = quadrant[i2];
+ if (s1 != s2) {
+ return (s1 > s2);
+ }
+ i1++;
+ i2++;
+
+ if (i1 > last) {
+ i1 -= last;
+ i1--;
+ }
+ if (i2 > last) {
+ i2 -= last;
+ i2--;
+ }
+
+ k -= 4;
+ workDone++;
+ } while (k >= 0);
+
+ return false;
+ }
+
+ /*
+ Knuth's increments seem to work better
+ than Incerpi-Sedgewick here. Possibly
+ because the number of elems to sort is
+ usually small, typically <= 20.
+ */
+ private int[] incs = { 1, 4, 13, 40, 121, 364, 1093, 3280,
+ 9841, 29524, 88573, 265720,
+ 797161, 2391484 };
+
+ private void allocateCompressStructures () {
+ int n = baseBlockSize * blockSize100k;
+ block = new char[(n + 1 + NUM_OVERSHOOT_BYTES)];
+ quadrant = new int[(n + NUM_OVERSHOOT_BYTES)];
+ zptr = new int[n];
+ ftab = new int[65537];
+
+ if (block == null || quadrant == null || zptr == null
+ || ftab == null) {
+ //int totalDraw = (n + 1 + NUM_OVERSHOOT_BYTES) + (n + NUM_OVERSHOOT_BYTES) + n + 65537;
+ //compressOutOfMemory ( totalDraw, n );
+ }
+
+ /*
+ The back end needs a place to store the MTF values
+ whilst it calculates the coding tables. We could
+ put them in the zptr array. However, these values
+ will fit in a short, so we overlay szptr at the
+ start of zptr, in the hope of reducing the number
+ of cache misses induced by the multiple traversals
+ of the MTF values when calculating coding tables.
+ Seems to improve compression speed by about 1%.
+ */
+ // szptr = zptr;
+
+
+ szptr = new short[2 * n];
+ }
+
+ private void generateMTFValues() {
+ char[] yy = new char[256];
+ int i, j;
+ char tmp;
+ char tmp2;
+ int zPend;
+ int wr;
+ int EOB;
+
+ makeMaps();
+ EOB = nInUse + 1;
+
+ for (i = 0; i <= EOB; i++) {
+ mtfFreq[i] = 0;
+ }
+
+ wr = 0;
+ zPend = 0;
+ for (i = 0; i < nInUse; i++) {
+ yy[i] = (char) i;
+ }
+
+
+ for (i = 0; i <= last; i++) {
+ char ll_i;
+
+ ll_i = unseqToSeq[block[zptr[i]]];
+
+ j = 0;
+ tmp = yy[j];
+ while (ll_i != tmp) {
+ j++;
+ tmp2 = tmp;
+ tmp = yy[j];
+ yy[j] = tmp2;
+ }
+ yy[0] = tmp;
+
+ if (j == 0) {
+ zPend++;
+ } else {
+ if (zPend > 0) {
+ zPend--;
+ while (true) {
+ switch (zPend % 2) {
+ case 0:
+ szptr[wr] = (short) RUNA;
+ wr++;
+ mtfFreq[RUNA]++;
+ break;
+ case 1:
+ szptr[wr] = (short) RUNB;
+ wr++;
+ mtfFreq[RUNB]++;
+ break;
+ }
+ if (zPend < 2) {
+ break;
+ }
+ zPend = (zPend - 2) / 2;
+ }
+ zPend = 0;
+ }
+ szptr[wr] = (short) (j + 1);
+ wr++;
+ mtfFreq[j + 1]++;
+ }
+ }
+
+ if (zPend > 0) {
+ zPend--;
+ while (true) {
+ switch (zPend % 2) {
+ case 0:
+ szptr[wr] = (short) RUNA;
+ wr++;
+ mtfFreq[RUNA]++;
+ break;
+ case 1:
+ szptr[wr] = (short) RUNB;
+ wr++;
+ mtfFreq[RUNB]++;
+ break;
+ }
+ if (zPend < 2) {
+ break;
+ }
+ zPend = (zPend - 2) / 2;
+ }
+ }
+
+ szptr[wr] = (short) EOB;
+ wr++;
+ mtfFreq[EOB]++;
+
+ nMTF = wr;
+ }
+}
+
+
diff --git a/pg/src/main/java/org/bouncycastle/apache/bzip2/CRC.java b/pg/src/main/java/org/bouncycastle/apache/bzip2/CRC.java
new file mode 100644
index 00000000..ce03d288
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/apache/bzip2/CRC.java
@@ -0,0 +1,131 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ *
+ */
+
+/*
+ * This package is based on the work done by Keiron Liddle, Aftex Software
+ * <keiron@aftexsw.com> to whom the Ant project is very grateful for his
+ * great code.
+ */
+
+package org.bouncycastle.apache.bzip2;
+
+/**
+ * A simple class the hold and calculate the CRC for sanity checking
+ * of the data.
+ *
+ * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a>
+ */
+class CRC {
+ public static int crc32Table[] = {
+ 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
+ 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
+ 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
+ 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
+ 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
+ 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
+ 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
+ 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
+ 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
+ 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
+ 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
+ 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
+ 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
+ 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
+ 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
+ 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
+ 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
+ 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
+ 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
+ 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
+ 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
+ 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
+ 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
+ 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
+ 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
+ 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
+ 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
+ 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
+ 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
+ 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
+ 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
+ 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
+ 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
+ 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
+ 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
+ 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
+ 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
+ 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
+ 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
+ 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
+ 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
+ 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
+ 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
+ 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
+ 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
+ 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
+ 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
+ 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
+ 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
+ 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
+ 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
+ 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
+ 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
+ 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
+ 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
+ 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
+ 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
+ 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
+ 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
+ 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
+ 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
+ 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
+ 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
+ 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
+ };
+
+ public CRC() {
+ initialiseCRC();
+ }
+
+ void initialiseCRC() {
+ globalCrc = 0xffffffff;
+ }
+
+ int getFinalCRC() {
+ return ~globalCrc;
+ }
+
+ int getGlobalCRC() {
+ return globalCrc;
+ }
+
+ void setGlobalCRC(int newCrc) {
+ globalCrc = newCrc;
+ }
+
+ void updateCRC(int inCh) {
+ int temp = (globalCrc >> 24) ^ inCh;
+ if (temp < 0) {
+ temp = 256 + temp;
+ }
+ globalCrc = (globalCrc << 8) ^ CRC.crc32Table[temp];
+ }
+
+ int globalCrc;
+}
+
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/ArmoredInputStream.java b/pg/src/main/java/org/bouncycastle/bcpg/ArmoredInputStream.java
new file mode 100644
index 00000000..802cdf91
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/ArmoredInputStream.java
@@ -0,0 +1,473 @@
+package org.bouncycastle.bcpg;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Vector;
+
+/**
+ * reader for Base64 armored objects - read the headers and then start returning
+ * bytes when the data is reached. An IOException is thrown if the CRC check
+ * fails.
+ */
+public class ArmoredInputStream
+ extends InputStream
+{
+ /*
+ * set up the decoding table.
+ */
+ private static final byte[] decodingTable;
+
+ static
+ {
+ decodingTable = new byte[128];
+
+ for (int i = 'A'; i <= 'Z'; i++)
+ {
+ decodingTable[i] = (byte)(i - 'A');
+ }
+
+ for (int i = 'a'; i <= 'z'; i++)
+ {
+ decodingTable[i] = (byte)(i - 'a' + 26);
+ }
+
+ for (int i = '0'; i <= '9'; i++)
+ {
+ decodingTable[i] = (byte)(i - '0' + 52);
+ }
+
+ decodingTable['+'] = 62;
+ decodingTable['/'] = 63;
+ }
+
+ /**
+ * decode the base 64 encoded input data.
+ *
+ * @return the offset the data starts in out.
+ */
+ private int decode(
+ int in0,
+ int in1,
+ int in2,
+ int in3,
+ int[] out)
+ throws EOFException
+ {
+ int b1, b2, b3, b4;
+
+ if (in3 < 0)
+ {
+ throw new EOFException("unexpected end of file in armored stream.");
+ }
+
+ if (in2 == '=')
+ {
+ b1 = decodingTable[in0] &0xff;
+ b2 = decodingTable[in1] & 0xff;
+
+ out[2] = ((b1 << 2) | (b2 >> 4)) & 0xff;
+
+ return 2;
+ }
+ else if (in3 == '=')
+ {
+ b1 = decodingTable[in0];
+ b2 = decodingTable[in1];
+ b3 = decodingTable[in2];
+
+ out[1] = ((b1 << 2) | (b2 >> 4)) & 0xff;
+ out[2] = ((b2 << 4) | (b3 >> 2)) & 0xff;
+
+ return 1;
+ }
+ else
+ {
+ b1 = decodingTable[in0];
+ b2 = decodingTable[in1];
+ b3 = decodingTable[in2];
+ b4 = decodingTable[in3];
+
+ out[0] = ((b1 << 2) | (b2 >> 4)) & 0xff;
+ out[1] = ((b2 << 4) | (b3 >> 2)) & 0xff;
+ out[2] = ((b3 << 6) | b4) & 0xff;
+
+ return 0;
+ }
+ }
+
+ InputStream in;
+ boolean start = true;
+ int[] outBuf = new int[3];
+ int bufPtr = 3;
+ CRC24 crc = new CRC24();
+ boolean crcFound = false;
+ boolean hasHeaders = true;
+ String header = null;
+ boolean newLineFound = false;
+ boolean clearText = false;
+ boolean restart = false;
+ Vector headerList= new Vector();
+ int lastC = 0;
+ boolean isEndOfStream;
+
+ /**
+ * Create a stream for reading a PGP armoured message, parsing up to a header
+ * and then reading the data that follows.
+ *
+ * @param in
+ */
+ public ArmoredInputStream(
+ InputStream in)
+ throws IOException
+ {
+ this(in, true);
+ }
+
+ /**
+ * Create an armoured input stream which will assume the data starts
+ * straight away, or parse for headers first depending on the value of
+ * hasHeaders.
+ *
+ * @param in
+ * @param hasHeaders true if headers are to be looked for, false otherwise.
+ */
+ public ArmoredInputStream(
+ InputStream in,
+ boolean hasHeaders)
+ throws IOException
+ {
+ this.in = in;
+ this.hasHeaders = hasHeaders;
+
+ if (hasHeaders)
+ {
+ parseHeaders();
+ }
+
+ start = false;
+ }
+
+ public int available()
+ throws IOException
+ {
+ return in.available();
+ }
+
+ private boolean parseHeaders()
+ throws IOException
+ {
+ header = null;
+
+ int c;
+ int last = 0;
+ boolean headerFound = false;
+
+ headerList = new Vector();
+
+ //
+ // if restart we already have a header
+ //
+ if (restart)
+ {
+ headerFound = true;
+ }
+ else
+ {
+ while ((c = in.read()) >= 0)
+ {
+ if (c == '-' && (last == 0 || last == '\n' || last == '\r'))
+ {
+ headerFound = true;
+ break;
+ }
+
+ last = c;
+ }
+ }
+
+ if (headerFound)
+ {
+ StringBuffer buf = new StringBuffer("-");
+ boolean eolReached = false;
+ boolean crLf = false;
+
+ if (restart) // we've had to look ahead two '-'
+ {
+ buf.append('-');
+ }
+
+ while ((c = in.read()) >= 0)
+ {
+ if (last == '\r' && c == '\n')
+ {
+ crLf = true;
+ }
+ if (eolReached && (last != '\r' && c == '\n'))
+ {
+ break;
+ }
+ if (eolReached && c == '\r')
+ {
+ break;
+ }
+ if (c == '\r' || (last != '\r' && c == '\n'))
+ {
+ String line = buf.toString();
+ if (line.trim().length() == 0)
+ {
+ break;
+ }
+ headerList.addElement(line);
+ buf.setLength(0);
+ }
+
+ if (c != '\n' && c != '\r')
+ {
+ buf.append((char)c);
+ eolReached = false;
+ }
+ else
+ {
+ if (c == '\r' || (last != '\r' && c == '\n'))
+ {
+ eolReached = true;
+ }
+ }
+
+ last = c;
+ }
+
+ if (crLf)
+ {
+ in.read(); // skip last \n
+ }
+ }
+
+ if (headerList.size() > 0)
+ {
+ header = (String)headerList.elementAt(0);
+ }
+
+ clearText = "-----BEGIN PGP SIGNED MESSAGE-----".equals(header);
+ newLineFound = true;
+
+ return headerFound;
+ }
+
+ /**
+ * @return true if we are inside the clear text section of a PGP
+ * signed message.
+ */
+ public boolean isClearText()
+ {
+ return clearText;
+ }
+
+ /**
+ * @return true if the stream is actually at end of file.
+ */
+ public boolean isEndOfStream()
+ {
+ return isEndOfStream;
+ }
+
+ /**
+ * Return the armor header line (if there is one)
+ * @return the armor header line, null if none present.
+ */
+ public String getArmorHeaderLine()
+ {
+ return header;
+ }
+
+ /**
+ * Return the armor headers (the lines after the armor header line),
+ * @return an array of armor headers, null if there aren't any.
+ */
+ public String[] getArmorHeaders()
+ {
+ if (headerList.size() <= 1)
+ {
+ return null;
+ }
+
+ String[] hdrs = new String[headerList.size() - 1];
+
+ for (int i = 0; i != hdrs.length; i++)
+ {
+ hdrs[i] = (String)headerList.elementAt(i + 1);
+ }
+
+ return hdrs;
+ }
+
+ private int readIgnoreSpace()
+ throws IOException
+ {
+ int c = in.read();
+
+ while (c == ' ' || c == '\t')
+ {
+ c = in.read();
+ }
+
+ return c;
+ }
+
+ public int read()
+ throws IOException
+ {
+ int c;
+
+ if (start)
+ {
+ if (hasHeaders)
+ {
+ parseHeaders();
+ }
+
+ crc.reset();
+ start = false;
+ }
+
+ if (clearText)
+ {
+ c = in.read();
+
+ if (c == '\r' || (c == '\n' && lastC != '\r'))
+ {
+ newLineFound = true;
+ }
+ else if (newLineFound && c == '-')
+ {
+ c = in.read();
+ if (c == '-') // a header, not dash escaped
+ {
+ clearText = false;
+ start = true;
+ restart = true;
+ }
+ else // a space - must be a dash escape
+ {
+ c = in.read();
+ }
+ newLineFound = false;
+ }
+ else
+ {
+ if (c != '\n' && lastC != '\r')
+ {
+ newLineFound = false;
+ }
+ }
+
+ lastC = c;
+
+ if (c < 0)
+ {
+ isEndOfStream = true;
+ }
+
+ return c;
+ }
+
+ if (bufPtr > 2 || crcFound)
+ {
+ c = readIgnoreSpace();
+
+ if (c == '\r' || c == '\n')
+ {
+ c = readIgnoreSpace();
+
+ while (c == '\n' || c == '\r')
+ {
+ c = readIgnoreSpace();
+ }
+
+ if (c < 0) // EOF
+ {
+ isEndOfStream = true;
+ return -1;
+ }
+
+ if (c == '=') // crc reached
+ {
+ bufPtr = decode(readIgnoreSpace(), readIgnoreSpace(), readIgnoreSpace(), readIgnoreSpace(), outBuf);
+ if (bufPtr == 0)
+ {
+ int i = ((outBuf[0] & 0xff) << 16)
+ | ((outBuf[1] & 0xff) << 8)
+ | (outBuf[2] & 0xff);
+
+ crcFound = true;
+
+ if (i != crc.getValue())
+ {
+ throw new IOException("crc check failed in armored message.");
+ }
+ return read();
+ }
+ else
+ {
+ throw new IOException("no crc found in armored message.");
+ }
+ }
+ else if (c == '-') // end of record reached
+ {
+ while ((c = in.read()) >= 0)
+ {
+ if (c == '\n' || c == '\r')
+ {
+ break;
+ }
+ }
+
+ if (!crcFound)
+ {
+ throw new IOException("crc check not found.");
+ }
+
+ crcFound = false;
+ start = true;
+ bufPtr = 3;
+
+ if (c < 0)
+ {
+ isEndOfStream = true;
+ }
+
+ return -1;
+ }
+ else // data
+ {
+ bufPtr = decode(c, readIgnoreSpace(), readIgnoreSpace(), readIgnoreSpace(), outBuf);
+ }
+ }
+ else
+ {
+ if (c >= 0)
+ {
+ bufPtr = decode(c, readIgnoreSpace(), readIgnoreSpace(), readIgnoreSpace(), outBuf);
+ }
+ else
+ {
+ isEndOfStream = true;
+ return -1;
+ }
+ }
+ }
+
+ c = outBuf[bufPtr++];
+
+ crc.update(c);
+
+ return c;
+ }
+
+ public void close()
+ throws IOException
+ {
+ in.close();
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/ArmoredOutputStream.java b/pg/src/main/java/org/bouncycastle/bcpg/ArmoredOutputStream.java
new file mode 100644
index 00000000..9e51fa12
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/ArmoredOutputStream.java
@@ -0,0 +1,411 @@
+package org.bouncycastle.bcpg;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * Basic output stream.
+ */
+public class ArmoredOutputStream
+ extends OutputStream
+{
+ private static final byte[] encodingTable =
+ {
+ (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
+ (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
+ (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
+ (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
+ (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
+ (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
+ (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
+ (byte)'v',
+ (byte)'w', (byte)'x', (byte)'y', (byte)'z',
+ (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6',
+ (byte)'7', (byte)'8', (byte)'9',
+ (byte)'+', (byte)'/'
+ };
+
+ /**
+ * encode the input data producing a base 64 encoded byte array.
+ */
+ private void encode(
+ OutputStream out,
+ int[] data,
+ int len)
+ throws IOException
+ {
+ int d1, d2, d3;
+
+ switch (len)
+ {
+ case 0: /* nothing left to do */
+ break;
+ case 1:
+ d1 = data[0];
+
+ out.write(encodingTable[(d1 >>> 2) & 0x3f]);
+ out.write(encodingTable[(d1 << 4) & 0x3f]);
+ out.write('=');
+ out.write('=');
+ break;
+ case 2:
+ d1 = data[0];
+ d2 = data[1];
+
+ out.write(encodingTable[(d1 >>> 2) & 0x3f]);
+ out.write(encodingTable[((d1 << 4) | (d2 >>> 4)) & 0x3f]);
+ out.write(encodingTable[(d2 << 2) & 0x3f]);
+ out.write('=');
+ break;
+ case 3:
+ d1 = data[0];
+ d2 = data[1];
+ d3 = data[2];
+
+ out.write(encodingTable[(d1 >>> 2) & 0x3f]);
+ out.write(encodingTable[((d1 << 4) | (d2 >>> 4)) & 0x3f]);
+ out.write(encodingTable[((d2 << 2) | (d3 >>> 6)) & 0x3f]);
+ out.write(encodingTable[d3 & 0x3f]);
+ break;
+ default:
+ throw new IOException("unknown length in encode");
+ }
+ }
+
+ OutputStream out;
+ int[] buf = new int[3];
+ int bufPtr = 0;
+ CRC24 crc = new CRC24();
+ int chunkCount = 0;
+ int lastb;
+
+ boolean start = true;
+ boolean clearText = false;
+ boolean newLine = false;
+
+ String nl = System.getProperty("line.separator");
+
+ String type;
+ String headerStart = "-----BEGIN PGP ";
+ String headerTail = "-----";
+ String footerStart = "-----END PGP ";
+ String footerTail = "-----";
+
+ String version = "BCPG v@RELEASE_NAME@";
+
+ Hashtable headers = new Hashtable();
+
+ public ArmoredOutputStream(
+ OutputStream out)
+ {
+ this.out = out;
+
+ if (nl == null)
+ {
+ nl = "\r\n";
+ }
+
+ resetHeaders();
+ }
+
+ public ArmoredOutputStream(
+ OutputStream out,
+ Hashtable headers)
+ {
+ this(out);
+
+ Enumeration e = headers.keys();
+
+ while (e.hasMoreElements())
+ {
+ Object key = e.nextElement();
+
+ this.headers.put(key, headers.get(key));
+ }
+ }
+
+ /**
+ * Set an additional header entry.
+ *
+ * @param name the name of the header entry.
+ * @param value the value of the header entry.
+ */
+ public void setHeader(
+ String name,
+ String value)
+ {
+ this.headers.put(name, value);
+ }
+
+ /**
+ * Reset the headers to only contain a Version string.
+ */
+ public void resetHeaders()
+ {
+ headers.clear();
+ headers.put("Version", version);
+ }
+
+ /**
+ * Start a clear text signed message.
+ * @param hashAlgorithm
+ */
+ public void beginClearText(
+ int hashAlgorithm)
+ throws IOException
+ {
+ String hash;
+
+ switch (hashAlgorithm)
+ {
+ case HashAlgorithmTags.SHA1:
+ hash = "SHA1";
+ break;
+ case HashAlgorithmTags.SHA256:
+ hash = "SHA256";
+ break;
+ case HashAlgorithmTags.SHA384:
+ hash = "SHA384";
+ break;
+ case HashAlgorithmTags.SHA512:
+ hash = "SHA512";
+ break;
+ case HashAlgorithmTags.MD2:
+ hash = "MD2";
+ break;
+ case HashAlgorithmTags.MD5:
+ hash = "MD5";
+ break;
+ case HashAlgorithmTags.RIPEMD160:
+ hash = "RIPEMD160";
+ break;
+ default:
+ throw new IOException("unknown hash algorithm tag in beginClearText: " + hashAlgorithm);
+ }
+
+ String armorHdr = "-----BEGIN PGP SIGNED MESSAGE-----" + nl;
+ String hdrs = "Hash: " + hash + nl + nl;
+
+ for (int i = 0; i != armorHdr.length(); i++)
+ {
+ out.write(armorHdr.charAt(i));
+ }
+
+ for (int i = 0; i != hdrs.length(); i++)
+ {
+ out.write(hdrs.charAt(i));
+ }
+
+ clearText = true;
+ newLine = true;
+ lastb = 0;
+ }
+
+ public void endClearText()
+ {
+ clearText = false;
+ }
+
+ private void writeHeaderEntry(
+ String name,
+ String value)
+ throws IOException
+ {
+ for (int i = 0; i != name.length(); i++)
+ {
+ out.write(name.charAt(i));
+ }
+
+ out.write(':');
+ out.write(' ');
+
+ for (int i = 0; i != value.length(); i++)
+ {
+ out.write(value.charAt(i));
+ }
+
+ for (int i = 0; i != nl.length(); i++)
+ {
+ out.write(nl.charAt(i));
+ }
+ }
+
+ public void write(
+ int b)
+ throws IOException
+ {
+ if (clearText)
+ {
+ out.write(b);
+
+ if (newLine)
+ {
+ if (!(b == '\n' && lastb == '\r'))
+ {
+ newLine = false;
+ }
+ if (b == '-')
+ {
+ out.write(' ');
+ out.write('-'); // dash escape
+ }
+ }
+ if (b == '\r' || (b == '\n' && lastb != '\r'))
+ {
+ newLine = true;
+ }
+ lastb = b;
+ return;
+ }
+
+ if (start)
+ {
+ boolean newPacket = (b & 0x40) != 0;
+ int tag = 0;
+
+ if (newPacket)
+ {
+ tag = b & 0x3f;
+ }
+ else
+ {
+ tag = (b & 0x3f) >> 2;
+ }
+
+ switch (tag)
+ {
+ case PacketTags.PUBLIC_KEY:
+ type = "PUBLIC KEY BLOCK";
+ break;
+ case PacketTags.SECRET_KEY:
+ type = "PRIVATE KEY BLOCK";
+ break;
+ case PacketTags.SIGNATURE:
+ type = "SIGNATURE";
+ break;
+ default:
+ type = "MESSAGE";
+ }
+
+ for (int i = 0; i != headerStart.length(); i++)
+ {
+ out.write(headerStart.charAt(i));
+ }
+
+ for (int i = 0; i != type.length(); i++)
+ {
+ out.write(type.charAt(i));
+ }
+
+ for (int i = 0; i != headerTail.length(); i++)
+ {
+ out.write(headerTail.charAt(i));
+ }
+
+ for (int i = 0; i != nl.length(); i++)
+ {
+ out.write(nl.charAt(i));
+ }
+
+ writeHeaderEntry("Version", (String)headers.get("Version"));
+
+ Enumeration e = headers.keys();
+ while (e.hasMoreElements())
+ {
+ String key = (String)e.nextElement();
+
+ if (!key.equals("Version"))
+ {
+ writeHeaderEntry(key, (String)headers.get(key));
+ }
+ }
+
+ for (int i = 0; i != nl.length(); i++)
+ {
+ out.write(nl.charAt(i));
+ }
+
+ start = false;
+ }
+
+ if (bufPtr == 3)
+ {
+ encode(out, buf, bufPtr);
+ bufPtr = 0;
+ if ((++chunkCount & 0xf) == 0)
+ {
+ for (int i = 0; i != nl.length(); i++)
+ {
+ out.write(nl.charAt(i));
+ }
+ }
+ }
+
+ crc.update(b);
+ buf[bufPtr++] = b & 0xff;
+ }
+
+ public void flush()
+ throws IOException
+ {
+ }
+
+ /**
+ * <b>Note</b>: close does nor close the underlying stream. So it is possible to write
+ * multiple objects using armoring to a single stream.
+ */
+ public void close()
+ throws IOException
+ {
+ if (type != null)
+ {
+ encode(out, buf, bufPtr);
+
+ for (int i = 0; i != nl.length(); i++)
+ {
+ out.write(nl.charAt(i));
+ }
+ out.write('=');
+
+ int crcV = crc.getValue();
+
+ buf[0] = ((crcV >> 16) & 0xff);
+ buf[1] = ((crcV >> 8) & 0xff);
+ buf[2] = (crcV & 0xff);
+
+ encode(out, buf, 3);
+
+ for (int i = 0; i != nl.length(); i++)
+ {
+ out.write(nl.charAt(i));
+ }
+
+ for (int i = 0; i != footerStart.length(); i++)
+ {
+ out.write(footerStart.charAt(i));
+ }
+
+ for (int i = 0; i != type.length(); i++)
+ {
+ out.write(type.charAt(i));
+ }
+
+ for (int i = 0; i != footerTail.length(); i++)
+ {
+ out.write(footerTail.charAt(i));
+ }
+
+ for (int i = 0; i != nl.length(); i++)
+ {
+ out.write(nl.charAt(i));
+ }
+
+ out.flush();
+
+ type = null;
+ start = true;
+ }
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/BCPGInputStream.java b/pg/src/main/java/org/bouncycastle/bcpg/BCPGInputStream.java
new file mode 100644
index 00000000..1e2e0723
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/BCPGInputStream.java
@@ -0,0 +1,391 @@
+package org.bouncycastle.bcpg;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.util.io.Streams;
+
+/**
+ * reader for PGP objects
+ */
+public class BCPGInputStream
+ extends InputStream implements PacketTags
+{
+ InputStream in;
+ boolean next = false;
+ int nextB;
+
+ public BCPGInputStream(
+ InputStream in)
+ {
+ this.in = in;
+ }
+
+ public int available()
+ throws IOException
+ {
+ return in.available();
+ }
+
+ public int read()
+ throws IOException
+ {
+ if (next)
+ {
+ next = false;
+
+ return nextB;
+ }
+ else
+ {
+ return in.read();
+ }
+ }
+
+ public int read(
+ byte[] buf,
+ int off,
+ int len)
+ throws IOException
+ {
+ if (len == 0)
+ {
+ return 0;
+ }
+
+ if (!next)
+ {
+ return in.read(buf, off, len);
+ }
+
+ // We have next byte waiting, so return it
+
+ if (nextB < 0)
+ {
+ return -1; // EOF
+ }
+
+ buf[off] = (byte)nextB; // May throw NullPointerException...
+ next = false; // ...so only set this afterwards
+
+ return 1;
+ }
+
+ public void readFully(
+ byte[] buf,
+ int off,
+ int len)
+ throws IOException
+ {
+ if (Streams.readFully(this, buf, off, len) < len)
+ {
+ throw new EOFException();
+ }
+ }
+
+ public byte[] readAll()
+ throws IOException
+ {
+ return Streams.readAll(this);
+ }
+
+ public void readFully(
+ byte[] buf)
+ throws IOException
+ {
+ readFully(buf, 0, buf.length);
+ }
+
+ /**
+ * returns the next packet tag in the stream.
+ *
+ * @return the tag number.
+ *
+ * @throws IOException
+ */
+ public int nextPacketTag()
+ throws IOException
+ {
+ if (!next)
+ {
+ try
+ {
+ nextB = in.read();
+ }
+ catch (EOFException e)
+ {
+ nextB = -1;
+ }
+ }
+
+ next = true;
+
+ if (nextB >= 0)
+ {
+ if ((nextB & 0x40) != 0) // new
+ {
+ return (nextB & 0x3f);
+ }
+ else // old
+ {
+ return ((nextB & 0x3f) >> 2);
+ }
+ }
+
+ return nextB;
+ }
+
+ public Packet readPacket()
+ throws IOException
+ {
+ int hdr = this.read();
+
+ if (hdr < 0)
+ {
+ return null;
+ }
+
+ if ((hdr & 0x80) == 0)
+ {
+ throw new IOException("invalid header encountered");
+ }
+
+ boolean newPacket = (hdr & 0x40) != 0;
+ int tag = 0;
+ int bodyLen = 0;
+ boolean partial = false;
+
+ if (newPacket)
+ {
+ tag = hdr & 0x3f;
+
+ int l = this.read();
+
+ if (l < 192)
+ {
+ bodyLen = l;
+ }
+ else if (l <= 223)
+ {
+ int b = in.read();
+
+ bodyLen = ((l - 192) << 8) + (b) + 192;
+ }
+ else if (l == 255)
+ {
+ bodyLen = (in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read();
+ }
+ else
+ {
+ partial = true;
+ bodyLen = 1 << (l & 0x1f);
+ }
+ }
+ else
+ {
+ int lengthType = hdr & 0x3;
+
+ tag = (hdr & 0x3f) >> 2;
+
+ switch (lengthType)
+ {
+ case 0:
+ bodyLen = this.read();
+ break;
+ case 1:
+ bodyLen = (this.read() << 8) | this.read();
+ break;
+ case 2:
+ bodyLen = (this.read() << 24) | (this.read() << 16) | (this.read() << 8) | this.read();
+ break;
+ case 3:
+ partial = true;
+ break;
+ default:
+ throw new IOException("unknown length type encountered");
+ }
+ }
+
+ BCPGInputStream objStream;
+
+ if (bodyLen == 0 && partial)
+ {
+ objStream = this;
+ }
+ else
+ {
+ objStream = new BCPGInputStream(new PartialInputStream(this, partial, bodyLen));
+ }
+
+ switch (tag)
+ {
+ case RESERVED:
+ return new InputStreamPacket(objStream);
+ case PUBLIC_KEY_ENC_SESSION:
+ return new PublicKeyEncSessionPacket(objStream);
+ case SIGNATURE:
+ return new SignaturePacket(objStream);
+ case SYMMETRIC_KEY_ENC_SESSION:
+ return new SymmetricKeyEncSessionPacket(objStream);
+ case ONE_PASS_SIGNATURE:
+ return new OnePassSignaturePacket(objStream);
+ case SECRET_KEY:
+ return new SecretKeyPacket(objStream);
+ case PUBLIC_KEY:
+ return new PublicKeyPacket(objStream);
+ case SECRET_SUBKEY:
+ return new SecretSubkeyPacket(objStream);
+ case COMPRESSED_DATA:
+ return new CompressedDataPacket(objStream);
+ case SYMMETRIC_KEY_ENC:
+ return new SymmetricEncDataPacket(objStream);
+ case MARKER:
+ return new MarkerPacket(objStream);
+ case LITERAL_DATA:
+ return new LiteralDataPacket(objStream);
+ case TRUST:
+ return new TrustPacket(objStream);
+ case USER_ID:
+ return new UserIDPacket(objStream);
+ case USER_ATTRIBUTE:
+ return new UserAttributePacket(objStream);
+ case PUBLIC_SUBKEY:
+ return new PublicSubkeyPacket(objStream);
+ case SYM_ENC_INTEGRITY_PRO:
+ return new SymmetricEncIntegrityPacket(objStream);
+ case MOD_DETECTION_CODE:
+ return new ModDetectionCodePacket(objStream);
+ case EXPERIMENTAL_1:
+ case EXPERIMENTAL_2:
+ case EXPERIMENTAL_3:
+ case EXPERIMENTAL_4:
+ return new ExperimentalPacket(tag, objStream);
+ default:
+ throw new IOException("unknown packet type encountered: " + tag);
+ }
+ }
+
+ public void close()
+ throws IOException
+ {
+ in.close();
+ }
+
+ /**
+ * a stream that overlays our input stream, allowing the user to only read a segment of it.
+ *
+ * NB: dataLength will be negative if the segment length is in the upper range above 2**31.
+ */
+ private static class PartialInputStream
+ extends InputStream
+ {
+ private BCPGInputStream in;
+ private boolean partial;
+ private int dataLength;
+
+ PartialInputStream(
+ BCPGInputStream in,
+ boolean partial,
+ int dataLength)
+ {
+ this.in = in;
+ this.partial = partial;
+ this.dataLength = dataLength;
+ }
+
+ public int available()
+ throws IOException
+ {
+ int avail = in.available();
+
+ if (avail <= dataLength || dataLength < 0)
+ {
+ return avail;
+ }
+ else
+ {
+ if (partial && dataLength == 0)
+ {
+ return 1;
+ }
+ return dataLength;
+ }
+ }
+
+ private int loadDataLength()
+ throws IOException
+ {
+ int l = in.read();
+
+ if (l < 0)
+ {
+ return -1;
+ }
+
+ partial = false;
+ if (l < 192)
+ {
+ dataLength = l;
+ }
+ else if (l <= 223)
+ {
+ dataLength = ((l - 192) << 8) + (in.read()) + 192;
+ }
+ else if (l == 255)
+ {
+ dataLength = (in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read();
+ }
+ else
+ {
+ partial = true;
+ dataLength = 1 << (l & 0x1f);
+ }
+
+ return dataLength;
+ }
+
+ public int read(byte[] buf, int offset, int len)
+ throws IOException
+ {
+ do
+ {
+ if (dataLength != 0)
+ {
+ int readLen = (dataLength > len || dataLength < 0) ? len : dataLength;
+ readLen = in.read(buf, offset, readLen);
+ if (readLen < 0)
+ {
+ throw new EOFException("premature end of stream in PartialInputStream");
+ }
+ dataLength -= readLen;
+ return readLen;
+ }
+ }
+ while (partial && loadDataLength() >= 0);
+
+ return -1;
+ }
+
+ public int read()
+ throws IOException
+ {
+ do
+ {
+ if (dataLength != 0)
+ {
+ int ch = in.read();
+ if (ch < 0)
+ {
+ throw new EOFException("premature end of stream in PartialInputStream");
+ }
+ dataLength--;
+ return ch;
+ }
+ }
+ while (partial && loadDataLength() >= 0);
+
+ return -1;
+ }
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/BCPGKey.java b/pg/src/main/java/org/bouncycastle/bcpg/BCPGKey.java
new file mode 100644
index 00000000..632ef91d
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/BCPGKey.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.bcpg;
+
+/**
+ * base interface for a PGP key
+ */
+public interface BCPGKey
+{
+ /**
+ * Return the base format for this key - in the case of the symmetric keys it will generally
+ * be raw indicating that the key is just a straight byte representation, for an asymmetric
+ * key the format will be PGP, indicating the key is a string of MPIs encoded in PGP format.
+ *
+ * @return "RAW" or "PGP"
+ */
+ public String getFormat();
+
+ /**
+ * return a string of bytes giving the encoded format of the key, as described by it's format.
+ *
+ * @return byte[]
+ */
+ public byte[] getEncoded();
+
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/BCPGObject.java b/pg/src/main/java/org/bouncycastle/bcpg/BCPGObject.java
new file mode 100644
index 00000000..7b05d8a8
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/BCPGObject.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.bcpg;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * base class for a PGP object.
+ */
+public abstract class BCPGObject
+{
+ public byte[] getEncoded()
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream pOut = new BCPGOutputStream(bOut);
+
+ pOut.writeObject(this);
+
+ return bOut.toByteArray();
+ }
+
+ public abstract void encode(BCPGOutputStream out)
+ throws IOException;
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/BCPGOutputStream.java b/pg/src/main/java/org/bouncycastle/bcpg/BCPGOutputStream.java
new file mode 100644
index 00000000..640310b4
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/BCPGOutputStream.java
@@ -0,0 +1,361 @@
+package org.bouncycastle.bcpg;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Basic output stream.
+ */
+public class BCPGOutputStream
+ extends OutputStream
+ implements PacketTags, CompressionAlgorithmTags
+{
+ OutputStream out;
+ private byte[] partialBuffer;
+ private int partialBufferLength;
+ private int partialPower;
+ private int partialOffset;
+
+ private static final int BUF_SIZE_POWER = 16; // 2^16 size buffer on long files
+
+ public BCPGOutputStream(
+ OutputStream out)
+ {
+ this.out = out;
+ }
+
+ /**
+ * Create a stream representing an old style partial object.
+ *
+ * @param tag the packet tag for the object.
+ */
+ public BCPGOutputStream(
+ OutputStream out,
+ int tag)
+ throws IOException
+ {
+ this.out = out;
+ this.writeHeader(tag, true, true, 0);
+ }
+
+ /**
+ * Create a stream representing a general packet.
+ *
+ * @param out
+ * @param tag
+ * @param length
+ * @param oldFormat
+ * @throws IOException
+ */
+ public BCPGOutputStream(
+ OutputStream out,
+ int tag,
+ long length,
+ boolean oldFormat)
+ throws IOException
+ {
+ this.out = out;
+
+ if (length > 0xFFFFFFFFL)
+ {
+ this.writeHeader(tag, false, true, 0);
+ this.partialBufferLength = 1 << BUF_SIZE_POWER;
+ this.partialBuffer = new byte[partialBufferLength];
+ this.partialPower = BUF_SIZE_POWER;
+ this.partialOffset = 0;
+ }
+ else
+ {
+ this.writeHeader(tag, oldFormat, false, length);
+ }
+ }
+
+ /**
+ *
+ * @param tag
+ * @param length
+ * @throws IOException
+ */
+ public BCPGOutputStream(
+ OutputStream out,
+ int tag,
+ long length)
+ throws IOException
+ {
+ this.out = out;
+
+ this.writeHeader(tag, false, false, length);
+ }
+
+ /**
+ * Create a new style partial input stream buffered into chunks.
+ *
+ * @param out output stream to write to.
+ * @param tag packet tag.
+ * @param buffer size of chunks making up the packet.
+ * @throws IOException
+ */
+ public BCPGOutputStream(
+ OutputStream out,
+ int tag,
+ byte[] buffer)
+ throws IOException
+ {
+ this.out = out;
+ this.writeHeader(tag, false, true, 0);
+
+ this.partialBuffer = buffer;
+
+ int length = partialBuffer.length;
+
+ for (partialPower = 0; length != 1; partialPower++)
+ {
+ length >>>= 1;
+ }
+
+ if (partialPower > 30)
+ {
+ throw new IOException("Buffer cannot be greater than 2^30 in length.");
+ }
+
+ this.partialBufferLength = 1 << partialPower;
+ this.partialOffset = 0;
+ }
+
+ private void writeNewPacketLength(
+ long bodyLen)
+ throws IOException
+ {
+ if (bodyLen < 192)
+ {
+ out.write((byte)bodyLen);
+ }
+ else if (bodyLen <= 8383)
+ {
+ bodyLen -= 192;
+
+ out.write((byte)(((bodyLen >> 8) & 0xff) + 192));
+ out.write((byte)bodyLen);
+ }
+ else
+ {
+ out.write(0xff);
+ out.write((byte)(bodyLen >> 24));
+ out.write((byte)(bodyLen >> 16));
+ out.write((byte)(bodyLen >> 8));
+ out.write((byte)bodyLen);
+ }
+ }
+
+ private void writeHeader(
+ int tag,
+ boolean oldPackets,
+ boolean partial,
+ long bodyLen)
+ throws IOException
+ {
+ int hdr = 0x80;
+
+ if (partialBuffer != null)
+ {
+ partialFlush(true);
+ partialBuffer = null;
+ }
+
+ if (oldPackets)
+ {
+ hdr |= tag << 2;
+
+ if (partial)
+ {
+ this.write(hdr | 0x03);
+ }
+ else
+ {
+ if (bodyLen <= 0xff)
+ {
+ this.write(hdr);
+ this.write((byte)bodyLen);
+ }
+ else if (bodyLen <= 0xffff)
+ {
+ this.write(hdr | 0x01);
+ this.write((byte)(bodyLen >> 8));
+ this.write((byte)(bodyLen));
+ }
+ else
+ {
+ this.write(hdr | 0x02);
+ this.write((byte)(bodyLen >> 24));
+ this.write((byte)(bodyLen >> 16));
+ this.write((byte)(bodyLen >> 8));
+ this.write((byte)bodyLen);
+ }
+ }
+ }
+ else
+ {
+ hdr |= 0x40 | tag;
+ this.write(hdr);
+
+ if (partial)
+ {
+ partialOffset = 0;
+ }
+ else
+ {
+ this.writeNewPacketLength(bodyLen);
+ }
+ }
+ }
+
+ private void partialFlush(
+ boolean isLast)
+ throws IOException
+ {
+ if (isLast)
+ {
+ writeNewPacketLength(partialOffset);
+ out.write(partialBuffer, 0, partialOffset);
+ }
+ else
+ {
+ out.write(0xE0 | partialPower);
+ out.write(partialBuffer, 0, partialBufferLength);
+ }
+
+ partialOffset = 0;
+ }
+
+ private void writePartial(
+ byte b)
+ throws IOException
+ {
+ if (partialOffset == partialBufferLength)
+ {
+ partialFlush(false);
+ }
+
+ partialBuffer[partialOffset++] = b;
+ }
+
+ private void writePartial(
+ byte[] buf,
+ int off,
+ int len)
+ throws IOException
+ {
+ if (partialOffset == partialBufferLength)
+ {
+ partialFlush(false);
+ }
+
+ if (len <= (partialBufferLength - partialOffset))
+ {
+ System.arraycopy(buf, off, partialBuffer, partialOffset, len);
+ partialOffset += len;
+ }
+ else
+ {
+ System.arraycopy(buf, off, partialBuffer, partialOffset, partialBufferLength - partialOffset);
+ off += partialBufferLength - partialOffset;
+ len -= partialBufferLength - partialOffset;
+ partialFlush(false);
+
+ while (len > partialBufferLength)
+ {
+ System.arraycopy(buf, off, partialBuffer, 0, partialBufferLength);
+ off += partialBufferLength;
+ len -= partialBufferLength;
+ partialFlush(false);
+ }
+
+ System.arraycopy(buf, off, partialBuffer, 0, len);
+ partialOffset += len;
+ }
+ }
+
+ public void write(
+ int b)
+ throws IOException
+ {
+ if (partialBuffer != null)
+ {
+ writePartial((byte)b);
+ }
+ else
+ {
+ out.write(b);
+ }
+ }
+
+ public void write(
+ byte[] bytes,
+ int off,
+ int len)
+ throws IOException
+ {
+ if (partialBuffer != null)
+ {
+ writePartial(bytes, off, len);
+ }
+ else
+ {
+ out.write(bytes, off, len);
+ }
+ }
+
+ public void writePacket(
+ ContainedPacket p)
+ throws IOException
+ {
+ p.encode(this);
+ }
+
+ void writePacket(
+ int tag,
+ byte[] body,
+ boolean oldFormat)
+ throws IOException
+ {
+ this.writeHeader(tag, oldFormat, false, body.length);
+ this.write(body);
+ }
+
+ public void writeObject(
+ BCPGObject o)
+ throws IOException
+ {
+ o.encode(this);
+ }
+
+ /**
+ * Flush the underlying stream.
+ */
+ public void flush()
+ throws IOException
+ {
+ out.flush();
+ }
+
+ /**
+ * Finish writing out the current packet without closing the underlying stream.
+ */
+ public void finish()
+ throws IOException
+ {
+ if (partialBuffer != null)
+ {
+ partialFlush(true);
+ partialBuffer = null;
+ }
+ }
+
+ public void close()
+ throws IOException
+ {
+ this.finish();
+ out.flush();
+ out.close();
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/CRC24.java b/pg/src/main/java/org/bouncycastle/bcpg/CRC24.java
new file mode 100644
index 00000000..82c4b9a8
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/CRC24.java
@@ -0,0 +1,37 @@
+package org.bouncycastle.bcpg;
+
+public class CRC24
+{
+ private static final int CRC24_INIT = 0x0b704ce;
+ private static final int CRC24_POLY = 0x1864cfb;
+
+ private int crc = CRC24_INIT;
+
+ public CRC24()
+ {
+ }
+
+ public void update(
+ int b)
+ {
+ crc ^= b << 16;
+ for (int i = 0; i < 8; i++)
+ {
+ crc <<= 1;
+ if ((crc & 0x1000000) != 0)
+ {
+ crc ^= CRC24_POLY;
+ }
+ }
+ }
+
+ public int getValue()
+ {
+ return crc;
+ }
+
+ public void reset()
+ {
+ crc = CRC24_INIT;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/CompressedDataPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/CompressedDataPacket.java
new file mode 100644
index 00000000..0b400591
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/CompressedDataPacket.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.bcpg;
+
+import java.io.IOException;
+
+/**
+ * generic compressed data object.
+ */
+public class CompressedDataPacket
+ extends InputStreamPacket
+{
+ int algorithm;
+
+ CompressedDataPacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ super(in);
+
+ algorithm = in.read();
+ }
+
+ /**
+ * return the algorithm tag value.
+ *
+ * @return algorithm tag value.
+ */
+ public int getAlgorithm()
+ {
+ return algorithm;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/CompressionAlgorithmTags.java b/pg/src/main/java/org/bouncycastle/bcpg/CompressionAlgorithmTags.java
new file mode 100644
index 00000000..5b684cf0
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/CompressionAlgorithmTags.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.bcpg;
+
+/**
+ * Basic tags for compression algorithms
+ */
+public interface CompressionAlgorithmTags
+{
+ public static final int UNCOMPRESSED = 0; // Uncompressed
+ public static final int ZIP = 1; // ZIP (RFC 1951)
+ public static final int ZLIB = 2; // ZLIB (RFC 1950)
+ public static final int BZIP2 = 3; // BZ2
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/ContainedPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/ContainedPacket.java
new file mode 100644
index 00000000..fca0078c
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/ContainedPacket.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.bcpg;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Basic type for a PGP packet.
+ */
+public abstract class ContainedPacket
+ extends Packet
+{
+ public byte[] getEncoded()
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream pOut = new BCPGOutputStream(bOut);
+
+ pOut.writePacket(this);
+
+ return bOut.toByteArray();
+ }
+
+ public abstract void encode(
+ BCPGOutputStream pOut)
+ throws IOException;
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/DSAPublicBCPGKey.java b/pg/src/main/java/org/bouncycastle/bcpg/DSAPublicBCPGKey.java
new file mode 100644
index 00000000..c2315174
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/DSAPublicBCPGKey.java
@@ -0,0 +1,116 @@
+package org.bouncycastle.bcpg;
+
+import java.io.*;
+import java.math.BigInteger;
+
+/**
+ * base class for a DSA Public Key.
+ */
+public class DSAPublicBCPGKey
+ extends BCPGObject implements BCPGKey
+{
+ MPInteger p;
+ MPInteger q;
+ MPInteger g;
+ MPInteger y;
+
+ /**
+ * @param in the stream to read the packet from.
+ */
+ public DSAPublicBCPGKey(
+ BCPGInputStream in)
+ throws IOException
+ {
+ this.p = new MPInteger(in);
+ this.q = new MPInteger(in);
+ this.g = new MPInteger(in);
+ this.y = new MPInteger(in);
+ }
+
+ public DSAPublicBCPGKey(
+ BigInteger p,
+ BigInteger q,
+ BigInteger g,
+ BigInteger y)
+ {
+ this.p = new MPInteger(p);
+ this.q = new MPInteger(q);
+ this.g = new MPInteger(g);
+ this.y = new MPInteger(y);
+ }
+
+ /**
+ * return "PGP"
+ *
+ * @see org.bouncycastle.bcpg.BCPGKey#getFormat()
+ */
+ public String getFormat()
+ {
+ return "PGP";
+ }
+
+ /**
+ * return the standard PGP encoding of the key.
+ *
+ * @see org.bouncycastle.bcpg.BCPGKey#getEncoded()
+ */
+ public byte[] getEncoded()
+ {
+ try
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream pgpOut = new BCPGOutputStream(bOut);
+
+ pgpOut.writeObject(this);
+
+ return bOut.toByteArray();
+ }
+ catch (IOException e)
+ {
+ return null;
+ }
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.writeObject(p);
+ out.writeObject(q);
+ out.writeObject(g);
+ out.writeObject(y);
+ }
+
+ /**
+ * @return g
+ */
+ public BigInteger getG()
+ {
+ return g.getValue();
+ }
+
+ /**
+ * @return p
+ */
+ public BigInteger getP()
+ {
+ return p.getValue();
+ }
+
+ /**
+ * @return q
+ */
+ public BigInteger getQ()
+ {
+ return q.getValue();
+ }
+
+ /**
+ * @return g
+ */
+ public BigInteger getY()
+ {
+ return y.getValue();
+ }
+
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/DSASecretBCPGKey.java b/pg/src/main/java/org/bouncycastle/bcpg/DSASecretBCPGKey.java
new file mode 100644
index 00000000..9f2d48f0
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/DSASecretBCPGKey.java
@@ -0,0 +1,82 @@
+package org.bouncycastle.bcpg;
+
+import java.io.*;
+import java.math.BigInteger;
+
+/**
+ * base class for a DSA Secret Key.
+ */
+public class DSASecretBCPGKey
+ extends BCPGObject implements BCPGKey
+{
+ MPInteger x;
+
+ /**
+ *
+ * @param in
+ * @throws IOException
+ */
+ public DSASecretBCPGKey(
+ BCPGInputStream in)
+ throws IOException
+ {
+ this.x = new MPInteger(in);
+ }
+
+ /**
+ *
+ * @param x
+ */
+ public DSASecretBCPGKey(
+ BigInteger x)
+ {
+ this.x = new MPInteger(x);
+ }
+
+ /**
+ * return "PGP"
+ *
+ * @see org.bouncycastle.bcpg.BCPGKey#getFormat()
+ */
+ public String getFormat()
+ {
+ return "PGP";
+ }
+
+ /**
+ * return the standard PGP encoding of the key.
+ *
+ * @see org.bouncycastle.bcpg.BCPGKey#getEncoded()
+ */
+ public byte[] getEncoded()
+ {
+ try
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream pgpOut = new BCPGOutputStream(bOut);
+
+ pgpOut.writeObject(this);
+
+ return bOut.toByteArray();
+ }
+ catch (IOException e)
+ {
+ return null;
+ }
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.writeObject(x);
+ }
+
+ /**
+ * @return x
+ */
+ public BigInteger getX()
+ {
+ return x.getValue();
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/ElGamalPublicBCPGKey.java b/pg/src/main/java/org/bouncycastle/bcpg/ElGamalPublicBCPGKey.java
new file mode 100644
index 00000000..8c273682
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/ElGamalPublicBCPGKey.java
@@ -0,0 +1,93 @@
+package org.bouncycastle.bcpg;
+
+import java.io.*;
+import java.math.BigInteger;
+
+/**
+ * base class for an ElGamal Public Key.
+ */
+public class ElGamalPublicBCPGKey
+ extends BCPGObject implements BCPGKey
+{
+ MPInteger p;
+ MPInteger g;
+ MPInteger y;
+
+ /**
+ *
+ */
+ public ElGamalPublicBCPGKey(
+ BCPGInputStream in)
+ throws IOException
+ {
+ this.p = new MPInteger(in);
+ this.g = new MPInteger(in);
+ this.y = new MPInteger(in);
+ }
+
+ public ElGamalPublicBCPGKey(
+ BigInteger p,
+ BigInteger g,
+ BigInteger y)
+ {
+ this.p = new MPInteger(p);
+ this.g = new MPInteger(g);
+ this.y = new MPInteger(y);
+ }
+
+ /**
+ * return "PGP"
+ *
+ * @see org.bouncycastle.bcpg.BCPGKey#getFormat()
+ */
+ public String getFormat()
+ {
+ return "PGP";
+ }
+
+ /**
+ * return the standard PGP encoding of the key.
+ *
+ * @see org.bouncycastle.bcpg.BCPGKey#getEncoded()
+ */
+ public byte[] getEncoded()
+ {
+ try
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream pgpOut = new BCPGOutputStream(bOut);
+
+ pgpOut.writeObject(this);
+
+ return bOut.toByteArray();
+ }
+ catch (IOException e)
+ {
+ return null;
+ }
+ }
+
+ public BigInteger getP()
+ {
+ return p.getValue();
+ }
+
+ public BigInteger getG()
+ {
+ return g.getValue();
+ }
+
+ public BigInteger getY()
+ {
+ return y.getValue();
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.writeObject(p);
+ out.writeObject(g);
+ out.writeObject(y);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/ElGamalSecretBCPGKey.java b/pg/src/main/java/org/bouncycastle/bcpg/ElGamalSecretBCPGKey.java
new file mode 100644
index 00000000..31c113ce
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/ElGamalSecretBCPGKey.java
@@ -0,0 +1,79 @@
+package org.bouncycastle.bcpg;
+
+import java.io.*;
+import java.math.BigInteger;
+
+/**
+ * base class for an ElGamal Secret Key.
+ */
+public class ElGamalSecretBCPGKey
+ extends BCPGObject implements BCPGKey
+{
+ MPInteger x;
+
+ /**
+ *
+ * @param in
+ * @throws IOException
+ */
+ public ElGamalSecretBCPGKey(
+ BCPGInputStream in)
+ throws IOException
+ {
+ this.x = new MPInteger(in);
+ }
+
+ /**
+ *
+ * @param x
+ */
+ public ElGamalSecretBCPGKey(
+ BigInteger x)
+ {
+ this.x = new MPInteger(x);
+ }
+
+ /**
+ * return "PGP"
+ *
+ * @see org.bouncycastle.bcpg.BCPGKey#getFormat()
+ */
+ public String getFormat()
+ {
+ return "PGP";
+ }
+
+ public BigInteger getX()
+ {
+ return x.getValue();
+ }
+
+ /**
+ * return the standard PGP encoding of the key.
+ *
+ * @see org.bouncycastle.bcpg.BCPGKey#getEncoded()
+ */
+ public byte[] getEncoded()
+ {
+ try
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream pgpOut = new BCPGOutputStream(bOut);
+
+ pgpOut.writeObject(this);
+
+ return bOut.toByteArray();
+ }
+ catch (IOException e)
+ {
+ return null;
+ }
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.writeObject(x);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/ExperimentalPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/ExperimentalPacket.java
new file mode 100644
index 00000000..8407052b
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/ExperimentalPacket.java
@@ -0,0 +1,46 @@
+package org.bouncycastle.bcpg;
+
+import java.io.IOException;
+
+import org.bouncycastle.util.Arrays;
+
+/**
+ * basic packet for an experimental packet.
+ */
+public class ExperimentalPacket
+ extends ContainedPacket implements PublicKeyAlgorithmTags
+{
+ private int tag;
+ private byte[] contents;
+
+ /**
+ *
+ * @param in
+ * @throws IOException
+ */
+ ExperimentalPacket(
+ int tag,
+ BCPGInputStream in)
+ throws IOException
+ {
+ this.tag = tag;
+ this.contents = in.readAll();
+ }
+
+ public int getTag()
+ {
+ return tag;
+ }
+
+ public byte[] getContents()
+ {
+ return Arrays.clone(contents);
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.writePacket(tag, contents, true);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/HashAlgorithmTags.java b/pg/src/main/java/org/bouncycastle/bcpg/HashAlgorithmTags.java
new file mode 100644
index 00000000..da52bc85
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/HashAlgorithmTags.java
@@ -0,0 +1,20 @@
+package org.bouncycastle.bcpg;
+
+/**
+ * basic tags for hash algorithms
+ */
+public interface HashAlgorithmTags
+{
+ public static final int MD5 = 1; // MD5
+ public static final int SHA1 = 2; // SHA-1
+ public static final int RIPEMD160 = 3; // RIPE-MD/160
+ public static final int DOUBLE_SHA = 4; // Reserved for double-width SHA (experimental)
+ public static final int MD2 = 5; // MD2
+ public static final int TIGER_192 = 6; // Reserved for TIGER/192
+ public static final int HAVAL_5_160 = 7; // Reserved for HAVAL (5 pass, 160-bit)
+
+ public static final int SHA256 = 8; // SHA-256
+ public static final int SHA384 = 9; // SHA-384
+ public static final int SHA512 = 10; // SHA-512
+ public static final int SHA224 = 11; // SHA-224
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/InputStreamPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/InputStreamPacket.java
new file mode 100644
index 00000000..411fa475
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/InputStreamPacket.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.bcpg;
+
+/**
+ *
+ */
+public class InputStreamPacket
+ extends Packet
+{
+ private BCPGInputStream in;
+
+ public InputStreamPacket(
+ BCPGInputStream in)
+ {
+ this.in = in;
+ }
+
+ /**
+ * Note: you can only read from this once...
+ *
+ * @return the InputStream
+ */
+ public BCPGInputStream getInputStream()
+ {
+ return in;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/LiteralDataPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/LiteralDataPacket.java
new file mode 100644
index 00000000..b660ce79
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/LiteralDataPacket.java
@@ -0,0 +1,74 @@
+package org.bouncycastle.bcpg;
+
+import java.io.IOException;
+
+import org.bouncycastle.util.Strings;
+
+/**
+ * generic literal data packet.
+ */
+public class LiteralDataPacket
+ extends InputStreamPacket
+{
+ int format;
+ byte[] fileName;
+ long modDate;
+
+ LiteralDataPacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ super(in);
+
+ format = in.read();
+ int l = in.read();
+
+ fileName = new byte[l];
+ for (int i = 0; i != fileName.length; i++)
+ {
+ fileName[i] = (byte)in.read();
+ }
+
+ modDate = ((long)in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read();
+ }
+
+ /**
+ * return the format tag value.
+ *
+ * @return format tag value.
+ */
+ public int getFormat()
+ {
+ return format;
+ }
+
+ /**
+ * Return the modification time of the file in milli-seconds.
+ *
+ * @return the modification time in millis
+ */
+ public long getModificationTime()
+ {
+ return modDate * 1000L;
+ }
+
+ /**
+ * @return filename
+ */
+ public String getFileName()
+ {
+ return Strings.fromUTF8ByteArray(fileName);
+ }
+
+ public byte[] getRawFileName()
+ {
+ byte[] tmp = new byte[fileName.length];
+
+ for (int i = 0; i != tmp.length; i++)
+ {
+ tmp[i] = fileName[i];
+ }
+
+ return tmp;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/MPInteger.java b/pg/src/main/java/org/bouncycastle/bcpg/MPInteger.java
new file mode 100644
index 00000000..ebd22615
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/MPInteger.java
@@ -0,0 +1,62 @@
+package org.bouncycastle.bcpg;
+
+import java.io.*;
+import java.math.BigInteger;
+
+/**
+ * a multiple precision integer
+ */
+public class MPInteger
+ extends BCPGObject
+{
+ BigInteger value = null;
+
+ public MPInteger(
+ BCPGInputStream in)
+ throws IOException
+ {
+ int length = (in.read() << 8) | in.read();
+ byte[] bytes = new byte[(length + 7) / 8];
+
+ in.readFully(bytes);
+
+ value = new BigInteger(1, bytes);
+ }
+
+ public MPInteger(
+ BigInteger value)
+ {
+ if (value == null || value.signum() < 0)
+ {
+ throw new IllegalArgumentException("value must not be null, or negative");
+ }
+
+ this.value = value;
+ }
+
+ public BigInteger getValue()
+ {
+ return value;
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ int length = value.bitLength();
+
+ out.write(length >> 8);
+ out.write(length);
+
+ byte[] bytes = value.toByteArray();
+
+ if (bytes[0] == 0)
+ {
+ out.write(bytes, 1, bytes.length - 1);
+ }
+ else
+ {
+ out.write(bytes, 0, bytes.length);
+ }
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/MarkerPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/MarkerPacket.java
new file mode 100644
index 00000000..a3e9757f
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/MarkerPacket.java
@@ -0,0 +1,28 @@
+package org.bouncycastle.bcpg;
+
+import java.io.IOException;
+
+/**
+ * Basic type for a marker packet
+ */
+public class MarkerPacket
+ extends ContainedPacket
+{
+ // "PGP"
+
+ byte[] marker = { (byte)0x50, (byte)0x47, (byte)0x50 };
+
+ public MarkerPacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ in.readFully(marker);
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.writePacket(MARKER, marker, true);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/ModDetectionCodePacket.java b/pg/src/main/java/org/bouncycastle/bcpg/ModDetectionCodePacket.java
new file mode 100644
index 00000000..812e9374
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/ModDetectionCodePacket.java
@@ -0,0 +1,45 @@
+package org.bouncycastle.bcpg;
+
+import java.io.*;
+
+/**
+ * basic packet for a modification detection code packet.
+ */
+public class ModDetectionCodePacket
+ extends ContainedPacket
+{
+ private byte[] digest;
+
+ ModDetectionCodePacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ this.digest = new byte[20];
+ in.readFully(this.digest);
+ }
+
+ public ModDetectionCodePacket(
+ byte[] digest)
+ throws IOException
+ {
+ this.digest = new byte[digest.length];
+
+ System.arraycopy(digest, 0, this.digest, 0, this.digest.length);
+ }
+
+ public byte[] getDigest()
+ {
+ byte[] tmp = new byte[digest.length];
+
+ System.arraycopy(digest, 0, tmp, 0, tmp.length);
+
+ return tmp;
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.writePacket(MOD_DETECTION_CODE, digest, false);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/OnePassSignaturePacket.java b/pg/src/main/java/org/bouncycastle/bcpg/OnePassSignaturePacket.java
new file mode 100644
index 00000000..fde95576
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/OnePassSignaturePacket.java
@@ -0,0 +1,115 @@
+package org.bouncycastle.bcpg;
+
+import java.io.*;
+
+/**
+ * generic signature object
+ */
+public class OnePassSignaturePacket
+ extends ContainedPacket
+{
+ private int version;
+ private int sigType;
+ private int hashAlgorithm;
+ private int keyAlgorithm;
+ private long keyID;
+ private int nested;
+
+ OnePassSignaturePacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ version = in.read();
+ sigType = in.read();
+ hashAlgorithm = in.read();
+ keyAlgorithm = in.read();
+
+ keyID |= (long)in.read() << 56;
+ keyID |= (long)in.read() << 48;
+ keyID |= (long)in.read() << 40;
+ keyID |= (long)in.read() << 32;
+ keyID |= (long)in.read() << 24;
+ keyID |= (long)in.read() << 16;
+ keyID |= (long)in.read() << 8;
+ keyID |= in.read();
+
+ nested = in.read();
+ }
+
+ public OnePassSignaturePacket(
+ int sigType,
+ int hashAlgorithm,
+ int keyAlgorithm,
+ long keyID,
+ boolean isNested)
+ {
+ this.version = 3;
+ this.sigType = sigType;
+ this.hashAlgorithm = hashAlgorithm;
+ this.keyAlgorithm = keyAlgorithm;
+ this.keyID = keyID;
+ this.nested = (isNested) ? 0 : 1;
+ }
+
+ /**
+ * Return the signature type.
+ * @return the signature type
+ */
+ public int getSignatureType()
+ {
+ return sigType;
+ }
+
+ /**
+ * return the encryption algorithm tag
+ */
+ public int getKeyAlgorithm()
+ {
+ return keyAlgorithm;
+ }
+
+ /**
+ * return the hashAlgorithm tag
+ */
+ public int getHashAlgorithm()
+ {
+ return hashAlgorithm;
+ }
+
+ /**
+ * @return long
+ */
+ public long getKeyID()
+ {
+ return keyID;
+ }
+
+ /**
+ *
+ */
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream pOut = new BCPGOutputStream(bOut);
+
+ pOut.write(version);
+ pOut.write(sigType);
+ pOut.write(hashAlgorithm);
+ pOut.write(keyAlgorithm);
+
+ pOut.write((byte)(keyID >> 56));
+ pOut.write((byte)(keyID >> 48));
+ pOut.write((byte)(keyID >> 40));
+ pOut.write((byte)(keyID >> 32));
+ pOut.write((byte)(keyID >> 24));
+ pOut.write((byte)(keyID >> 16));
+ pOut.write((byte)(keyID >> 8));
+ pOut.write((byte)(keyID));
+
+ pOut.write(nested);
+
+ out.writePacket(ONE_PASS_SIGNATURE, bOut.toByteArray(), true);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/OutputStreamPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/OutputStreamPacket.java
new file mode 100644
index 00000000..fcf3a97b
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/OutputStreamPacket.java
@@ -0,0 +1,18 @@
+package org.bouncycastle.bcpg;
+
+import java.io.IOException;
+
+public abstract class OutputStreamPacket
+{
+ protected BCPGOutputStream out;
+
+ public OutputStreamPacket(
+ BCPGOutputStream out)
+ {
+ this.out = out;
+ }
+
+ public abstract BCPGOutputStream open() throws IOException;
+
+ public abstract void close() throws IOException;
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/Packet.java b/pg/src/main/java/org/bouncycastle/bcpg/Packet.java
new file mode 100644
index 00000000..b233fd56
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/Packet.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.bcpg;
+
+/**
+ */
+public class Packet
+ implements PacketTags
+{
+
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/PacketTags.java b/pg/src/main/java/org/bouncycastle/bcpg/PacketTags.java
new file mode 100644
index 00000000..3fc163cb
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/PacketTags.java
@@ -0,0 +1,31 @@
+package org.bouncycastle.bcpg;
+
+/**
+ * Basic PGP packet tag types.
+ */
+public interface PacketTags
+{
+ public static final int RESERVED = 0 ; // Reserved - a packet tag must not have this value
+ public static final int PUBLIC_KEY_ENC_SESSION = 1; // Public-Key Encrypted Session Key Packet
+ public static final int SIGNATURE = 2; // Signature Packet
+ public static final int SYMMETRIC_KEY_ENC_SESSION = 3; // Symmetric-Key Encrypted Session Key Packet
+ public static final int ONE_PASS_SIGNATURE = 4 ; // One-Pass Signature Packet
+ public static final int SECRET_KEY = 5; // Secret Key Packet
+ public static final int PUBLIC_KEY = 6 ; // Public Key Packet
+ public static final int SECRET_SUBKEY = 7; // Secret Subkey Packet
+ public static final int COMPRESSED_DATA = 8; // Compressed Data Packet
+ public static final int SYMMETRIC_KEY_ENC = 9; // Symmetrically Encrypted Data Packet
+ public static final int MARKER = 10; // Marker Packet
+ public static final int LITERAL_DATA = 11; // Literal Data Packet
+ public static final int TRUST = 12; // Trust Packet
+ public static final int USER_ID = 13; // User ID Packet
+ public static final int PUBLIC_SUBKEY = 14; // Public Subkey Packet
+ public static final int USER_ATTRIBUTE = 17; // User attribute
+ public static final int SYM_ENC_INTEGRITY_PRO = 18; // Symmetric encrypted, integrity protected
+ public static final int MOD_DETECTION_CODE = 19; // Modification detection code
+
+ public static final int EXPERIMENTAL_1 = 60; // Private or Experimental Values
+ public static final int EXPERIMENTAL_2 = 61;
+ public static final int EXPERIMENTAL_3 = 62;
+ public static final int EXPERIMENTAL_4 = 63;
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyAlgorithmTags.java b/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyAlgorithmTags.java
new file mode 100644
index 00000000..7e8e86f3
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyAlgorithmTags.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.bcpg;
+
+/**
+ * Public Key Algorithm tag numbers
+ */
+public interface PublicKeyAlgorithmTags
+{
+ public static final int RSA_GENERAL = 1; // RSA (Encrypt or Sign)
+ public static final int RSA_ENCRYPT = 2; // RSA Encrypt-Only
+ public static final int RSA_SIGN = 3; // RSA Sign-Only
+ public static final int ELGAMAL_ENCRYPT = 16; // Elgamal (Encrypt-Only), see [ELGAMAL]
+ public static final int DSA = 17; // DSA (Digital Signature Standard)
+ public static final int EC = 18; // Reserved for Elliptic Curve
+ public static final int ECDSA = 19; // Reserved for ECDSA
+ public static final int ELGAMAL_GENERAL = 20; // Elgamal (Encrypt or Sign)
+ public static final int DIFFIE_HELLMAN = 21; // Reserved for Diffie-Hellman (X9.42, as defined for IETF-S/MIME)
+
+ public static final int EXPERIMENTAL_1 = 100;
+ public static final int EXPERIMENTAL_2 = 101;
+ public static final int EXPERIMENTAL_3 = 102;
+ public static final int EXPERIMENTAL_4 = 103;
+ public static final int EXPERIMENTAL_5 = 104;
+ public static final int EXPERIMENTAL_6 = 105;
+ public static final int EXPERIMENTAL_7 = 106;
+ public static final int EXPERIMENTAL_8 = 107;
+ public static final int EXPERIMENTAL_9 = 108;
+ public static final int EXPERIMENTAL_10 = 109;
+ public static final int EXPERIMENTAL_11 = 110;
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyEncSessionPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyEncSessionPacket.java
new file mode 100644
index 00000000..9f7b7282
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyEncSessionPacket.java
@@ -0,0 +1,112 @@
+package org.bouncycastle.bcpg;
+
+import java.io.*;
+import java.math.BigInteger;
+
+/**
+ * basic packet for a PGP public key
+ */
+public class PublicKeyEncSessionPacket
+ extends ContainedPacket implements PublicKeyAlgorithmTags
+{
+ private int version;
+ private long keyID;
+ private int algorithm;
+ private BigInteger[] data;
+
+ PublicKeyEncSessionPacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ version = in.read();
+
+ keyID |= (long)in.read() << 56;
+ keyID |= (long)in.read() << 48;
+ keyID |= (long)in.read() << 40;
+ keyID |= (long)in.read() << 32;
+ keyID |= (long)in.read() << 24;
+ keyID |= (long)in.read() << 16;
+ keyID |= (long)in.read() << 8;
+ keyID |= in.read();
+
+ algorithm = in.read();
+
+ switch (algorithm)
+ {
+ case RSA_ENCRYPT:
+ case RSA_GENERAL:
+ data = new BigInteger[1];
+
+ data[0] = new MPInteger(in).getValue();
+ break;
+ case ELGAMAL_ENCRYPT:
+ case ELGAMAL_GENERAL:
+ data = new BigInteger[2];
+
+ data[0] = new MPInteger(in).getValue();
+ data[1] = new MPInteger(in).getValue();
+ break;
+ default:
+ throw new IOException("unknown PGP public key algorithm encountered");
+ }
+ }
+
+ public PublicKeyEncSessionPacket(
+ long keyID,
+ int algorithm,
+ BigInteger[] data)
+ {
+ this.version = 3;
+ this.keyID = keyID;
+ this.algorithm = algorithm;
+ this.data = data;
+ }
+
+ public int getVersion()
+ {
+ return version;
+ }
+
+ public long getKeyID()
+ {
+ return keyID;
+ }
+
+ public int getAlgorithm()
+ {
+ return algorithm;
+ }
+
+ public BigInteger[] getEncSessionKey()
+ {
+ return data;
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream pOut = new BCPGOutputStream(bOut);
+
+ pOut.write(version);
+
+ pOut.write((byte)(keyID >> 56));
+ pOut.write((byte)(keyID >> 48));
+ pOut.write((byte)(keyID >> 40));
+ pOut.write((byte)(keyID >> 32));
+ pOut.write((byte)(keyID >> 24));
+ pOut.write((byte)(keyID >> 16));
+ pOut.write((byte)(keyID >> 8));
+ pOut.write((byte)(keyID));
+
+ pOut.write(algorithm);
+
+ for (int i = 0; i != data.length; i++)
+ {
+ pOut.writeObject(new MPInteger(data[i]));
+ }
+
+ out.writePacket(PUBLIC_KEY_ENC_SESSION , bOut.toByteArray(), true);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyPacket.java
new file mode 100644
index 00000000..fe402fe2
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyPacket.java
@@ -0,0 +1,126 @@
+package org.bouncycastle.bcpg;
+
+import java.io.*;
+import java.util.Date;
+
+/**
+ * basic packet for a PGP public key
+ */
+public class PublicKeyPacket
+ extends ContainedPacket implements PublicKeyAlgorithmTags
+{
+ private int version;
+ private long time;
+ private int validDays;
+ private int algorithm;
+ private BCPGKey key;
+
+ PublicKeyPacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ version = in.read();
+ time = ((long)in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read();
+
+ if (version <= 3)
+ {
+ validDays = (in.read() << 8) | in.read();
+ }
+
+ algorithm = (byte)in.read();
+
+ switch (algorithm)
+ {
+ case RSA_ENCRYPT:
+ case RSA_GENERAL:
+ case RSA_SIGN:
+ key = new RSAPublicBCPGKey(in);
+ break;
+ case DSA:
+ key = new DSAPublicBCPGKey(in);
+ break;
+ case ELGAMAL_ENCRYPT:
+ case ELGAMAL_GENERAL:
+ key = new ElGamalPublicBCPGKey(in);
+ break;
+ default:
+ throw new IOException("unknown PGP public key algorithm encountered");
+ }
+ }
+
+ /**
+ * Construct version 4 public key packet.
+ *
+ * @param algorithm
+ * @param time
+ * @param key
+ */
+ public PublicKeyPacket(
+ int algorithm,
+ Date time,
+ BCPGKey key)
+ {
+ this.version = 4;
+ this.time = time.getTime() / 1000;
+ this.algorithm = algorithm;
+ this.key = key;
+ }
+
+ public int getVersion()
+ {
+ return version;
+ }
+
+ public int getAlgorithm()
+ {
+ return algorithm;
+ }
+
+ public int getValidDays()
+ {
+ return validDays;
+ }
+
+ public Date getTime()
+ {
+ return new Date(time * 1000);
+ }
+
+ public BCPGKey getKey()
+ {
+ return key;
+ }
+
+ public byte[] getEncodedContents()
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream pOut = new BCPGOutputStream(bOut);
+
+ pOut.write(version);
+
+ pOut.write((byte)(time >> 24));
+ pOut.write((byte)(time >> 16));
+ pOut.write((byte)(time >> 8));
+ pOut.write((byte)time);
+
+ if (version <= 3)
+ {
+ pOut.write((byte)(validDays >> 8));
+ pOut.write((byte)validDays);
+ }
+
+ pOut.write(algorithm);
+
+ pOut.writeObject((BCPGObject)key);
+
+ return bOut.toByteArray();
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.writePacket(PUBLIC_KEY, getEncodedContents(), true);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/PublicSubkeyPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/PublicSubkeyPacket.java
new file mode 100644
index 00000000..e203fe37
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/PublicSubkeyPacket.java
@@ -0,0 +1,40 @@
+package org.bouncycastle.bcpg;
+
+import java.io.*;
+import java.util.Date;
+
+/**
+ * basic packet for a PGP public key
+ */
+public class PublicSubkeyPacket
+ extends PublicKeyPacket
+{
+ PublicSubkeyPacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ super(in);
+ }
+
+ /**
+ * Construct version 4 public key packet.
+ *
+ * @param algorithm
+ * @param time
+ * @param key
+ */
+ public PublicSubkeyPacket(
+ int algorithm,
+ Date time,
+ BCPGKey key)
+ {
+ super(algorithm, time, key);
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.writePacket(PUBLIC_SUBKEY, getEncodedContents(), true);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/RSAPublicBCPGKey.java b/pg/src/main/java/org/bouncycastle/bcpg/RSAPublicBCPGKey.java
new file mode 100644
index 00000000..1729f4d8
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/RSAPublicBCPGKey.java
@@ -0,0 +1,91 @@
+package org.bouncycastle.bcpg;
+
+import java.math.BigInteger;
+import java.io.*;
+
+/**
+ * base class for an RSA Public Key.
+ */
+public class RSAPublicBCPGKey
+ extends BCPGObject implements BCPGKey
+{
+ MPInteger n;
+ MPInteger e;
+
+ /**
+ * Construct an RSA public key from the passed in stream.
+ *
+ * @param in
+ * @throws IOException
+ */
+ public RSAPublicBCPGKey(
+ BCPGInputStream in)
+ throws IOException
+ {
+ this.n = new MPInteger(in);
+ this.e = new MPInteger(in);
+ }
+
+ /**
+ *
+ * @param n the modulus
+ * @param e the public exponent
+ */
+ public RSAPublicBCPGKey(
+ BigInteger n,
+ BigInteger e)
+ {
+ this.n = new MPInteger(n);
+ this.e = new MPInteger(e);
+ }
+
+ public BigInteger getPublicExponent()
+ {
+ return e.getValue();
+ }
+
+ public BigInteger getModulus()
+ {
+ return n.getValue();
+ }
+
+ /**
+ * return "PGP"
+ *
+ * @see org.bouncycastle.bcpg.BCPGKey#getFormat()
+ */
+ public String getFormat()
+ {
+ return "PGP";
+ }
+
+ /**
+ * return the standard PGP encoding of the key.
+ *
+ * @see org.bouncycastle.bcpg.BCPGKey#getEncoded()
+ */
+ public byte[] getEncoded()
+ {
+ try
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream pgpOut = new BCPGOutputStream(bOut);
+
+ pgpOut.writeObject(this);
+
+ return bOut.toByteArray();
+ }
+ catch (IOException e)
+ {
+ return null;
+ }
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.writeObject(n);
+ out.writeObject(e);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/RSASecretBCPGKey.java b/pg/src/main/java/org/bouncycastle/bcpg/RSASecretBCPGKey.java
new file mode 100644
index 00000000..6a6e7e6d
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/RSASecretBCPGKey.java
@@ -0,0 +1,176 @@
+package org.bouncycastle.bcpg;
+
+import java.io.*;
+import java.math.BigInteger;
+
+/**
+ * base class for an RSA Secret (or Private) Key.
+ */
+public class RSASecretBCPGKey
+ extends BCPGObject implements BCPGKey
+{
+ MPInteger d;
+ MPInteger p;
+ MPInteger q;
+ MPInteger u;
+
+ BigInteger expP, expQ, crt;
+
+ /**
+ *
+ * @param in
+ * @throws IOException
+ */
+ public RSASecretBCPGKey(
+ BCPGInputStream in)
+ throws IOException
+ {
+ this.d = new MPInteger(in);
+ this.p = new MPInteger(in);
+ this.q = new MPInteger(in);
+ this.u = new MPInteger(in);
+
+ expP = d.getValue().remainder(p.getValue().subtract(BigInteger.valueOf(1)));
+ expQ = d.getValue().remainder(q.getValue().subtract(BigInteger.valueOf(1)));
+ crt = q.getValue().modInverse(p.getValue());
+ }
+
+ /**
+ *
+ * @param d
+ * @param p
+ * @param q
+ */
+ public RSASecretBCPGKey(
+ BigInteger d,
+ BigInteger p,
+ BigInteger q)
+ {
+ //
+ // pgp requires (p < q)
+ //
+ int cmp = p.compareTo(q);
+ if (cmp >= 0)
+ {
+ if (cmp == 0)
+ {
+ throw new IllegalArgumentException("p and q cannot be equal");
+ }
+
+ BigInteger tmp = p;
+ p = q;
+ q = tmp;
+ }
+
+ this.d = new MPInteger(d);
+ this.p = new MPInteger(p);
+ this.q = new MPInteger(q);
+ this.u = new MPInteger(p.modInverse(q));
+
+ expP = d.remainder(p.subtract(BigInteger.valueOf(1)));
+ expQ = d.remainder(q.subtract(BigInteger.valueOf(1)));
+ crt = q.modInverse(p);
+ }
+
+ /**
+ * return the modulus for this key.
+ *
+ * @return BigInteger
+ */
+ public BigInteger getModulus()
+ {
+ return p.getValue().multiply(q.getValue());
+ }
+
+ /**
+ * return the private exponent for this key.
+ *
+ * @return BigInteger
+ */
+ public BigInteger getPrivateExponent()
+ {
+ return d.getValue();
+ }
+
+ /**
+ * return the prime P
+ */
+ public BigInteger getPrimeP()
+ {
+ return p.getValue();
+ }
+
+ /**
+ * return the prime Q
+ */
+ public BigInteger getPrimeQ()
+ {
+ return q.getValue();
+ }
+
+ /**
+ * return the prime exponent of p
+ */
+ public BigInteger getPrimeExponentP()
+ {
+ return expP;
+ }
+
+ /**
+ * return the prime exponent of q
+ */
+ public BigInteger getPrimeExponentQ()
+ {
+ return expQ;
+ }
+
+ /**
+ * return the crt coefficient
+ */
+ public BigInteger getCrtCoefficient()
+ {
+ return crt;
+ }
+
+ /**
+ * return "PGP"
+ *
+ * @see org.bouncycastle.bcpg.BCPGKey#getFormat()
+ */
+ public String getFormat()
+ {
+ return "PGP";
+ }
+
+ /**
+ * return the standard PGP encoding of the key.
+ *
+ * @see org.bouncycastle.bcpg.BCPGKey#getEncoded()
+ */
+ public byte[] getEncoded()
+ {
+ try
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream pgpOut = new BCPGOutputStream(bOut);
+
+ pgpOut.writeObject(this);
+
+ return bOut.toByteArray();
+ }
+ catch (IOException e)
+ {
+ return null;
+ }
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.writeObject(d);
+ out.writeObject(p);
+ out.writeObject(q);
+ out.writeObject(u);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/S2K.java b/pg/src/main/java/org/bouncycastle/bcpg/S2K.java
new file mode 100644
index 00000000..167e7155
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/S2K.java
@@ -0,0 +1,151 @@
+package org.bouncycastle.bcpg;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * The string to key specifier class
+ */
+public class S2K
+ extends BCPGObject
+{
+ private static final int EXPBIAS = 6;
+
+ public static final int SIMPLE = 0;
+ public static final int SALTED = 1;
+ public static final int SALTED_AND_ITERATED = 3;
+ public static final int GNU_DUMMY_S2K = 101;
+
+ int type;
+ int algorithm;
+ byte[] iv;
+ int itCount = -1;
+ int protectionMode = -1;
+
+ S2K(
+ InputStream in)
+ throws IOException
+ {
+ DataInputStream dIn = new DataInputStream(in);
+
+ type = dIn.read();
+ algorithm = dIn.read();
+
+ //
+ // if this happens we have a dummy-S2K packet.
+ //
+ if (type != GNU_DUMMY_S2K)
+ {
+ if (type != 0)
+ {
+ iv = new byte[8];
+ dIn.readFully(iv, 0, iv.length);
+
+ if (type == 3)
+ {
+ itCount = dIn.read();
+ }
+ }
+ }
+ else
+ {
+ dIn.read(); // G
+ dIn.read(); // N
+ dIn.read(); // U
+ protectionMode = dIn.read(); // protection mode
+ }
+ }
+
+ public S2K(
+ int algorithm)
+ {
+ this.type = 0;
+ this.algorithm = algorithm;
+ }
+
+ public S2K(
+ int algorithm,
+ byte[] iv)
+ {
+ this.type = 1;
+ this.algorithm = algorithm;
+ this.iv = iv;
+ }
+
+ public S2K(
+ int algorithm,
+ byte[] iv,
+ int itCount)
+ {
+ this.type = 3;
+ this.algorithm = algorithm;
+ this.iv = iv;
+ this.itCount = itCount;
+ }
+
+ public int getType()
+ {
+ return type;
+ }
+
+ /**
+ * return the hash algorithm for this S2K
+ */
+ public int getHashAlgorithm()
+ {
+ return algorithm;
+ }
+
+ /**
+ * return the iv for the key generation algorithm
+ */
+ public byte[] getIV()
+ {
+ return iv;
+ }
+
+ /**
+ * return the iteration count
+ */
+ public long getIterationCount()
+ {
+ return (16 + (itCount & 15)) << ((itCount >> 4) + EXPBIAS);
+ }
+
+ /**
+ * the protection mode - only if GNU_DUMMY_S2K
+ */
+ public int getProtectionMode()
+ {
+ return protectionMode;
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.write(type);
+ out.write(algorithm);
+
+ if (type != GNU_DUMMY_S2K)
+ {
+ if (type != 0)
+ {
+ out.write(iv);
+ }
+
+ if (type == 3)
+ {
+ out.write(itCount);
+ }
+ }
+ else
+ {
+ out.write('G');
+ out.write('N');
+ out.write('U');
+ out.write(protectionMode);
+ }
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java
new file mode 100644
index 00000000..d362bb93
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java
@@ -0,0 +1,185 @@
+package org.bouncycastle.bcpg;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * basic packet for a PGP secret key
+ */
+public class SecretKeyPacket
+ extends ContainedPacket implements PublicKeyAlgorithmTags
+{
+ public static final int USAGE_NONE = 0x00;
+ public static final int USAGE_CHECKSUM = 0xff;
+ public static final int USAGE_SHA1 = 0xfe;
+
+ private PublicKeyPacket pubKeyPacket;
+ private byte[] secKeyData;
+ private int s2kUsage;
+ private int encAlgorithm;
+ private S2K s2k;
+ private byte[] iv;
+
+ /**
+ *
+ * @param in
+ * @throws IOException
+ */
+ SecretKeyPacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ if (this instanceof SecretSubkeyPacket)
+ {
+ pubKeyPacket = new PublicSubkeyPacket(in);
+ }
+ else
+ {
+ pubKeyPacket = new PublicKeyPacket(in);
+ }
+
+ s2kUsage = in.read();
+
+ if (s2kUsage == USAGE_CHECKSUM || s2kUsage == USAGE_SHA1)
+ {
+ encAlgorithm = in.read();
+ s2k = new S2K(in);
+ }
+ else
+ {
+ encAlgorithm = s2kUsage;
+ }
+
+ if (!(s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K && s2k.getProtectionMode() == 0x01))
+ {
+ if (s2kUsage != 0)
+ {
+ if (encAlgorithm < 7)
+ {
+ iv = new byte[8];
+ }
+ else
+ {
+ iv = new byte[16];
+ }
+ in.readFully(iv, 0, iv.length);
+ }
+ }
+
+ this.secKeyData = in.readAll();
+ }
+
+ /**
+ *
+ * @param pubKeyPacket
+ * @param encAlgorithm
+ * @param s2k
+ * @param iv
+ * @param secKeyData
+ */
+ public SecretKeyPacket(
+ PublicKeyPacket pubKeyPacket,
+ int encAlgorithm,
+ S2K s2k,
+ byte[] iv,
+ byte[] secKeyData)
+ {
+ this.pubKeyPacket = pubKeyPacket;
+ this.encAlgorithm = encAlgorithm;
+
+ if (encAlgorithm != SymmetricKeyAlgorithmTags.NULL)
+ {
+ this.s2kUsage = USAGE_CHECKSUM;
+ }
+ else
+ {
+ this.s2kUsage = USAGE_NONE;
+ }
+
+ this.s2k = s2k;
+ this.iv = iv;
+ this.secKeyData = secKeyData;
+ }
+
+ public SecretKeyPacket(
+ PublicKeyPacket pubKeyPacket,
+ int encAlgorithm,
+ int s2kUsage,
+ S2K s2k,
+ byte[] iv,
+ byte[] secKeyData)
+ {
+ this.pubKeyPacket = pubKeyPacket;
+ this.encAlgorithm = encAlgorithm;
+ this.s2kUsage = s2kUsage;
+ this.s2k = s2k;
+ this.iv = iv;
+ this.secKeyData = secKeyData;
+ }
+
+ public int getEncAlgorithm()
+ {
+ return encAlgorithm;
+ }
+
+ public int getS2KUsage()
+ {
+ return s2kUsage;
+ }
+
+ public byte[] getIV()
+ {
+ return iv;
+ }
+
+ public S2K getS2K()
+ {
+ return s2k;
+ }
+
+ public PublicKeyPacket getPublicKeyPacket()
+ {
+ return pubKeyPacket;
+ }
+
+ public byte[] getSecretKeyData()
+ {
+ return secKeyData;
+ }
+
+ public byte[] getEncodedContents()
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream pOut = new BCPGOutputStream(bOut);
+
+ pOut.write(pubKeyPacket.getEncodedContents());
+
+ pOut.write(s2kUsage);
+
+ if (s2kUsage == USAGE_CHECKSUM || s2kUsage == USAGE_SHA1)
+ {
+ pOut.write(encAlgorithm);
+ pOut.writeObject(s2k);
+ }
+
+ if (iv != null)
+ {
+ pOut.write(iv);
+ }
+
+ if (secKeyData != null && secKeyData.length > 0)
+ {
+ pOut.write(secKeyData);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.writePacket(SECRET_KEY, getEncodedContents(), true);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/SecretSubkeyPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/SecretSubkeyPacket.java
new file mode 100644
index 00000000..728ab3d7
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/SecretSubkeyPacket.java
@@ -0,0 +1,58 @@
+package org.bouncycastle.bcpg;
+
+import java.io.*;
+
+/**
+ * basic packet for a PGP secret key
+ */
+public class SecretSubkeyPacket
+ extends SecretKeyPacket
+{
+ /**
+ *
+ * @param in
+ * @throws IOException
+ */
+ SecretSubkeyPacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ super(in);
+ }
+
+ /**
+ *
+ * @param pubKeyPacket
+ * @param encAlgorithm
+ * @param s2k
+ * @param iv
+ * @param secKeyData
+ */
+ public SecretSubkeyPacket(
+ PublicKeyPacket pubKeyPacket,
+ int encAlgorithm,
+ S2K s2k,
+ byte[] iv,
+ byte[] secKeyData)
+ {
+ super(pubKeyPacket, encAlgorithm, s2k, iv, secKeyData);
+ }
+
+ public SecretSubkeyPacket(
+ PublicKeyPacket pubKeyPacket,
+ int encAlgorithm,
+ int s2kUsage,
+ S2K s2k,
+ byte[] iv,
+ byte[] secKeyData)
+ {
+ super(pubKeyPacket, encAlgorithm, s2kUsage, s2k, iv, secKeyData);
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.writePacket(SECRET_SUBKEY, getEncodedContents(), true);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/SignaturePacket.java b/pg/src/main/java/org/bouncycastle/bcpg/SignaturePacket.java
new file mode 100644
index 00000000..5db5fb6e
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/SignaturePacket.java
@@ -0,0 +1,515 @@
+package org.bouncycastle.bcpg;
+
+import org.bouncycastle.bcpg.sig.IssuerKeyID;
+import org.bouncycastle.bcpg.sig.SignatureCreationTime;
+import org.bouncycastle.util.Arrays;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Vector;
+
+/**
+ * generic signature packet
+ */
+public class SignaturePacket
+ extends ContainedPacket implements PublicKeyAlgorithmTags
+{
+ private int version;
+ private int signatureType;
+ private long creationTime;
+ private long keyID;
+ private int keyAlgorithm;
+ private int hashAlgorithm;
+ private MPInteger[] signature;
+ private byte[] fingerPrint;
+ private SignatureSubpacket[] hashedData;
+ private SignatureSubpacket[] unhashedData;
+ private byte[] signatureEncoding;
+
+ SignaturePacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ version = in.read();
+
+ if (version == 3 || version == 2)
+ {
+ int l = in.read();
+
+ signatureType = in.read();
+ creationTime = (((long)in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read()) * 1000;
+ keyID |= (long)in.read() << 56;
+ keyID |= (long)in.read() << 48;
+ keyID |= (long)in.read() << 40;
+ keyID |= (long)in.read() << 32;
+ keyID |= (long)in.read() << 24;
+ keyID |= (long)in.read() << 16;
+ keyID |= (long)in.read() << 8;
+ keyID |= in.read();
+ keyAlgorithm = in.read();
+ hashAlgorithm = in.read();
+ }
+ else if (version == 4)
+ {
+ signatureType = in.read();
+ keyAlgorithm = in.read();
+ hashAlgorithm = in.read();
+
+ int hashedLength = (in.read() << 8) | in.read();
+ byte[] hashed = new byte[hashedLength];
+
+ in.readFully(hashed);
+
+ //
+ // read the signature sub packet data.
+ //
+ SignatureSubpacket sub;
+ SignatureSubpacketInputStream sIn = new SignatureSubpacketInputStream(
+ new ByteArrayInputStream(hashed));
+
+ Vector v = new Vector();
+ while ((sub = sIn.readPacket()) != null)
+ {
+ v.addElement(sub);
+ }
+
+ hashedData = new SignatureSubpacket[v.size()];
+
+ for (int i = 0; i != hashedData.length; i++)
+ {
+ SignatureSubpacket p = (SignatureSubpacket)v.elementAt(i);
+ if (p instanceof IssuerKeyID)
+ {
+ keyID = ((IssuerKeyID)p).getKeyID();
+ }
+ else if (p instanceof SignatureCreationTime)
+ {
+ creationTime = ((SignatureCreationTime)p).getTime().getTime();
+ }
+
+ hashedData[i] = p;
+ }
+
+ int unhashedLength = (in.read() << 8) | in.read();
+ byte[] unhashed = new byte[unhashedLength];
+
+ in.readFully(unhashed);
+
+ sIn = new SignatureSubpacketInputStream(
+ new ByteArrayInputStream(unhashed));
+
+ v.removeAllElements();
+ while ((sub = sIn.readPacket()) != null)
+ {
+ v.addElement(sub);
+ }
+
+ unhashedData = new SignatureSubpacket[v.size()];
+
+ for (int i = 0; i != unhashedData.length; i++)
+ {
+ SignatureSubpacket p = (SignatureSubpacket)v.elementAt(i);
+ if (p instanceof IssuerKeyID)
+ {
+ keyID = ((IssuerKeyID)p).getKeyID();
+ }
+
+ unhashedData[i] = p;
+ }
+ }
+ else
+ {
+ throw new RuntimeException("unsupported version: " + version);
+ }
+
+ fingerPrint = new byte[2];
+ in.readFully(fingerPrint);
+
+ switch (keyAlgorithm)
+ {
+ case RSA_GENERAL:
+ case RSA_SIGN:
+ MPInteger v = new MPInteger(in);
+
+ signature = new MPInteger[1];
+ signature[0] = v;
+ break;
+ case DSA:
+ MPInteger r = new MPInteger(in);
+ MPInteger s = new MPInteger(in);
+
+ signature = new MPInteger[2];
+ signature[0] = r;
+ signature[1] = s;
+ break;
+ case ELGAMAL_ENCRYPT: // yep, this really does happen sometimes.
+ case ELGAMAL_GENERAL:
+ MPInteger p = new MPInteger(in);
+ MPInteger g = new MPInteger(in);
+ MPInteger y = new MPInteger(in);
+
+ signature = new MPInteger[3];
+ signature[0] = p;
+ signature[1] = g;
+ signature[2] = y;
+ break;
+ default:
+ if (keyAlgorithm >= PublicKeyAlgorithmTags.EXPERIMENTAL_1 && keyAlgorithm <= PublicKeyAlgorithmTags.EXPERIMENTAL_11)
+ {
+ signature = null;
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ int ch;
+ while ((ch = in.read()) >= 0)
+ {
+ bOut.write(ch);
+ }
+ signatureEncoding = bOut.toByteArray();
+ }
+ else
+ {
+ throw new IOException("unknown signature key algorithm: " + keyAlgorithm);
+ }
+ }
+ }
+
+ /**
+ * Generate a version 4 signature packet.
+ *
+ * @param signatureType
+ * @param keyAlgorithm
+ * @param hashAlgorithm
+ * @param hashedData
+ * @param unhashedData
+ * @param fingerPrint
+ * @param signature
+ */
+ public SignaturePacket(
+ int signatureType,
+ long keyID,
+ int keyAlgorithm,
+ int hashAlgorithm,
+ SignatureSubpacket[] hashedData,
+ SignatureSubpacket[] unhashedData,
+ byte[] fingerPrint,
+ MPInteger[] signature)
+ {
+ this(4, signatureType, keyID, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerPrint, signature);
+ }
+
+ /**
+ * Generate a version 2/3 signature packet.
+ *
+ * @param signatureType
+ * @param keyAlgorithm
+ * @param hashAlgorithm
+ * @param fingerPrint
+ * @param signature
+ */
+ public SignaturePacket(
+ int version,
+ int signatureType,
+ long keyID,
+ int keyAlgorithm,
+ int hashAlgorithm,
+ long creationTime,
+ byte[] fingerPrint,
+ MPInteger[] signature)
+ {
+ this(version, signatureType, keyID, keyAlgorithm, hashAlgorithm, null, null, fingerPrint, signature);
+
+ this.creationTime = creationTime;
+ }
+
+ public SignaturePacket(
+ int version,
+ int signatureType,
+ long keyID,
+ int keyAlgorithm,
+ int hashAlgorithm,
+ SignatureSubpacket[] hashedData,
+ SignatureSubpacket[] unhashedData,
+ byte[] fingerPrint,
+ MPInteger[] signature)
+ {
+ this.version = version;
+ this.signatureType = signatureType;
+ this.keyID = keyID;
+ this.keyAlgorithm = keyAlgorithm;
+ this.hashAlgorithm = hashAlgorithm;
+ this.hashedData = hashedData;
+ this.unhashedData = unhashedData;
+ this.fingerPrint = fingerPrint;
+ this.signature = signature;
+
+ if (hashedData != null)
+ {
+ setCreationTime();
+ }
+ }
+
+ /**
+ * get the version number
+ */
+ public int getVersion()
+ {
+ return version;
+ }
+
+ /**
+ * return the signature type.
+ */
+ public int getSignatureType()
+ {
+ return signatureType;
+ }
+
+ /**
+ * return the keyID
+ * @return the keyID that created the signature.
+ */
+ public long getKeyID()
+ {
+ return keyID;
+ }
+
+ /**
+ * return the signature trailer that must be included with the data
+ * to reconstruct the signature
+ *
+ * @return byte[]
+ */
+ public byte[] getSignatureTrailer()
+ {
+ byte[] trailer = null;
+
+ if (version == 3 || version == 2)
+ {
+ trailer = new byte[5];
+
+ long time = creationTime / 1000;
+
+ trailer[0] = (byte)signatureType;
+ trailer[1] = (byte)(time >> 24);
+ trailer[2] = (byte)(time >> 16);
+ trailer[3] = (byte)(time >> 8);
+ trailer[4] = (byte)(time);
+ }
+ else
+ {
+ ByteArrayOutputStream sOut = new ByteArrayOutputStream();
+
+ try
+ {
+ sOut.write((byte)this.getVersion());
+ sOut.write((byte)this.getSignatureType());
+ sOut.write((byte)this.getKeyAlgorithm());
+ sOut.write((byte)this.getHashAlgorithm());
+
+ ByteArrayOutputStream hOut = new ByteArrayOutputStream();
+ SignatureSubpacket[] hashed = this.getHashedSubPackets();
+
+ for (int i = 0; i != hashed.length; i++)
+ {
+ hashed[i].encode(hOut);
+ }
+
+ byte[] data = hOut.toByteArray();
+
+ sOut.write((byte)(data.length >> 8));
+ sOut.write((byte)data.length);
+ sOut.write(data);
+
+ byte[] hData = sOut.toByteArray();
+
+ sOut.write((byte)this.getVersion());
+ 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));
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception generating trailer: " + e);
+ }
+
+ trailer = sOut.toByteArray();
+ }
+
+ return trailer;
+ }
+
+ /**
+ * return the encryption algorithm tag
+ */
+ public int getKeyAlgorithm()
+ {
+ return keyAlgorithm;
+ }
+
+ /**
+ * return the hashAlgorithm tag
+ */
+ public int getHashAlgorithm()
+ {
+ return hashAlgorithm;
+ }
+
+ /**
+ * return the signature as a set of integers - note this is normalised to be the
+ * ASN.1 encoding of what appears in the signature packet.
+ */
+ public MPInteger[] getSignature()
+ {
+ return signature;
+ }
+
+ /**
+ * Return the byte encoding of the signature section.
+ * @return uninterpreted signature bytes.
+ */
+ public byte[] getSignatureBytes()
+ {
+ if (signatureEncoding == null)
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream bcOut = new BCPGOutputStream(bOut);
+
+ for (int i = 0; i != signature.length; i++)
+ {
+ try
+ {
+ bcOut.writeObject(signature[i]);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("internal error: " + e);
+ }
+ }
+ return bOut.toByteArray();
+ }
+ else
+ {
+ return Arrays.clone(signatureEncoding);
+ }
+ }
+ public SignatureSubpacket[] getHashedSubPackets()
+ {
+ return hashedData;
+ }
+
+ public SignatureSubpacket[] getUnhashedSubPackets()
+ {
+ return unhashedData;
+ }
+
+ /**
+ * Return the creation time of the signature in milli-seconds.
+ *
+ * @return the creation time in millis
+ */
+ public long getCreationTime()
+ {
+ return creationTime;
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream pOut = new BCPGOutputStream(bOut);
+
+ pOut.write(version);
+
+ if (version == 3 || version == 2)
+ {
+ pOut.write(5); // the length of the next block
+
+ long time = creationTime / 1000;
+
+ pOut.write(signatureType);
+ pOut.write((byte)(time >> 24));
+ pOut.write((byte)(time >> 16));
+ pOut.write((byte)(time >> 8));
+ pOut.write((byte)time);
+
+ pOut.write((byte)(keyID >> 56));
+ pOut.write((byte)(keyID >> 48));
+ pOut.write((byte)(keyID >> 40));
+ pOut.write((byte)(keyID >> 32));
+ pOut.write((byte)(keyID >> 24));
+ pOut.write((byte)(keyID >> 16));
+ pOut.write((byte)(keyID >> 8));
+ pOut.write((byte)(keyID));
+
+ pOut.write(keyAlgorithm);
+ pOut.write(hashAlgorithm);
+ }
+ else if (version == 4)
+ {
+ pOut.write(signatureType);
+ pOut.write(keyAlgorithm);
+ pOut.write(hashAlgorithm);
+
+ ByteArrayOutputStream sOut = new ByteArrayOutputStream();
+
+ for (int i = 0; i != hashedData.length; i++)
+ {
+ hashedData[i].encode(sOut);
+ }
+
+ byte[] data = sOut.toByteArray();
+
+ pOut.write(data.length >> 8);
+ pOut.write(data.length);
+ pOut.write(data);
+
+ sOut.reset();
+
+ for (int i = 0; i != unhashedData.length; i++)
+ {
+ unhashedData[i].encode(sOut);
+ }
+
+ data = sOut.toByteArray();
+
+ pOut.write(data.length >> 8);
+ pOut.write(data.length);
+ pOut.write(data);
+ }
+ else
+ {
+ throw new IOException("unknown version: " + version);
+ }
+
+ pOut.write(fingerPrint);
+
+ if (signature != null)
+ {
+ for (int i = 0; i != signature.length; i++)
+ {
+ pOut.writeObject(signature[i]);
+ }
+ }
+ else
+ {
+ pOut.write(signatureEncoding);
+ }
+
+ out.writePacket(SIGNATURE, bOut.toByteArray(), true);
+ }
+
+ private void setCreationTime()
+ {
+ for (int i = 0; i != hashedData.length; i++)
+ {
+ if (hashedData[i] instanceof SignatureCreationTime)
+ {
+ creationTime = ((SignatureCreationTime)hashedData[i]).getTime().getTime();
+ break;
+ }
+ }
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/SignatureSubpacket.java b/pg/src/main/java/org/bouncycastle/bcpg/SignatureSubpacket.java
new file mode 100644
index 00000000..c44e9eac
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/SignatureSubpacket.java
@@ -0,0 +1,81 @@
+package org.bouncycastle.bcpg;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Basic type for a PGP Signature sub-packet.
+ */
+public class SignatureSubpacket
+{
+ int type;
+ boolean critical;
+
+ protected byte[] data;
+
+ protected SignatureSubpacket(
+ int type,
+ boolean critical,
+ byte[] data)
+ {
+ this.type = type;
+ this.critical = critical;
+ this.data = data;
+ }
+
+ public int getType()
+ {
+ return type;
+ }
+
+ public boolean isCritical()
+ {
+ return critical;
+ }
+
+ /**
+ * return the generic data making up the packet.
+ */
+ public byte[] getData()
+ {
+ return data;
+ }
+
+ public void encode(
+ OutputStream out)
+ throws IOException
+ {
+ int bodyLen = data.length + 1;
+
+ if (bodyLen < 192)
+ {
+ out.write((byte)bodyLen);
+ }
+ else if (bodyLen <= 8383)
+ {
+ bodyLen -= 192;
+
+ out.write((byte)(((bodyLen >> 8) & 0xff) + 192));
+ out.write((byte)bodyLen);
+ }
+ else
+ {
+ out.write(0xff);
+ out.write((byte)(bodyLen >> 24));
+ out.write((byte)(bodyLen >> 16));
+ out.write((byte)(bodyLen >> 8));
+ out.write((byte)bodyLen);
+ }
+
+ if (critical)
+ {
+ out.write(0x80 | type);
+ }
+ else
+ {
+ out.write(type);
+ }
+
+ out.write(data);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/SignatureSubpacketInputStream.java b/pg/src/main/java/org/bouncycastle/bcpg/SignatureSubpacketInputStream.java
new file mode 100644
index 00000000..e855e3bc
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/SignatureSubpacketInputStream.java
@@ -0,0 +1,123 @@
+package org.bouncycastle.bcpg;
+
+import org.bouncycastle.bcpg.sig.Exportable;
+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.SignatureCreationTime;
+import org.bouncycastle.bcpg.sig.SignatureExpirationTime;
+import org.bouncycastle.bcpg.sig.SignerUserID;
+import org.bouncycastle.bcpg.sig.TrustSignature;
+import org.bouncycastle.util.io.Streams;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * reader for signature sub-packets
+ */
+public class SignatureSubpacketInputStream
+ extends InputStream implements SignatureSubpacketTags
+{
+ InputStream in;
+
+ public SignatureSubpacketInputStream(
+ InputStream in)
+ {
+ this.in = in;
+ }
+
+ public int available()
+ throws IOException
+ {
+ return in.available();
+ }
+
+ public int read()
+ throws IOException
+ {
+ return in.read();
+ }
+
+ public SignatureSubpacket readPacket()
+ throws IOException
+ {
+ int l = this.read();
+ int bodyLen = 0;
+
+ if (l < 0)
+ {
+ return null;
+ }
+
+ if (l < 192)
+ {
+ bodyLen = l;
+ }
+ else if (l <= 223)
+ {
+ bodyLen = ((l - 192) << 8) + (in.read()) + 192;
+ }
+ else if (l == 255)
+ {
+ bodyLen = (in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read();
+ }
+ else
+ {
+ // TODO Error?
+ }
+
+ int tag = in.read();
+
+ if (tag < 0)
+ {
+ throw new EOFException("unexpected EOF reading signature sub packet");
+ }
+
+ byte[] data = new byte[bodyLen - 1];
+ if (Streams.readFully(in, data) < data.length)
+ {
+ throw new EOFException();
+ }
+
+ boolean isCritical = ((tag & 0x80) != 0);
+ int type = tag & 0x7f;
+
+ switch (type)
+ {
+ case CREATION_TIME:
+ return new SignatureCreationTime(isCritical, data);
+ case KEY_EXPIRE_TIME:
+ return new KeyExpirationTime(isCritical, data);
+ case EXPIRE_TIME:
+ return new SignatureExpirationTime(isCritical, data);
+ case REVOCABLE:
+ return new Revocable(isCritical, data);
+ case EXPORTABLE:
+ return new Exportable(isCritical, data);
+ case ISSUER_KEY_ID:
+ return new IssuerKeyID(isCritical, data);
+ case TRUST_SIG:
+ return new TrustSignature(isCritical, data);
+ case PREFERRED_COMP_ALGS:
+ case PREFERRED_HASH_ALGS:
+ case PREFERRED_SYM_ALGS:
+ return new PreferredAlgorithms(type, isCritical, data);
+ case KEY_FLAGS:
+ return new KeyFlags(isCritical, data);
+ case PRIMARY_USER_ID:
+ return new PrimaryUserID(isCritical, data);
+ case SIGNER_USER_ID:
+ return new SignerUserID(isCritical, data);
+ case NOTATION_DATA:
+ return new NotationData(isCritical, data);
+ }
+
+ return new SignatureSubpacket(type, isCritical, data);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/SignatureSubpacketTags.java b/pg/src/main/java/org/bouncycastle/bcpg/SignatureSubpacketTags.java
new file mode 100644
index 00000000..8c279586
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/SignatureSubpacketTags.java
@@ -0,0 +1,32 @@
+package org.bouncycastle.bcpg;
+
+/**
+ * Basic PGP signature sub-packet tag types.
+ */
+public interface SignatureSubpacketTags
+{
+ public static final int CREATION_TIME = 2; // signature creation time
+ public static final int EXPIRE_TIME = 3; // signature expiration time
+ public static final int EXPORTABLE = 4; // exportable certification
+ public static final int TRUST_SIG = 5; // trust signature
+ public static final int REG_EXP = 6; // regular expression
+ public static final int REVOCABLE = 7; // revocable
+ public static final int KEY_EXPIRE_TIME = 9; // key expiration time
+ public static final int PLACEHOLDER = 10; // placeholder for backward compatibility
+ public static final int PREFERRED_SYM_ALGS = 11; // preferred symmetric algorithms
+ public static final int REVOCATION_KEY = 12; // revocation key
+ public static final int ISSUER_KEY_ID = 16; // issuer key ID
+ public static final int NOTATION_DATA = 20; // notation data
+ public static final int PREFERRED_HASH_ALGS = 21; // preferred hash algorithms
+ public static final int PREFERRED_COMP_ALGS = 22; // preferred compression algorithms
+ public static final int KEY_SERVER_PREFS = 23; // key server preferences
+ public static final int PREFERRED_KEY_SERV = 24; // preferred key server
+ public static final int PRIMARY_USER_ID = 25; // primary user id
+ public static final int POLICY_URL = 26; // policy URL
+ public static final int KEY_FLAGS = 27; // key flags
+ public static final int SIGNER_USER_ID = 28; // signer's user id
+ public static final int REVOCATION_REASON = 29; // reason for revocation
+ public static final int FEATURES = 30; // features
+ public static final int SIGNATURE_TARGET = 31; // signature target
+ public static final int EMBEDDED_SIGNATURE = 32; // embedded signature
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/SymmetricEncDataPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/SymmetricEncDataPacket.java
new file mode 100644
index 00000000..6efe350a
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/SymmetricEncDataPacket.java
@@ -0,0 +1,14 @@
+package org.bouncycastle.bcpg;
+
+/**
+ * Basic type for a symmetric key encrypted packet
+ */
+public class SymmetricEncDataPacket
+ extends InputStreamPacket
+{
+ public SymmetricEncDataPacket(
+ BCPGInputStream in)
+ {
+ super(in);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/SymmetricEncIntegrityPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/SymmetricEncIntegrityPacket.java
new file mode 100644
index 00000000..edf6e2ad
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/SymmetricEncIntegrityPacket.java
@@ -0,0 +1,20 @@
+package org.bouncycastle.bcpg;
+
+import java.io.IOException;
+
+/**
+ */
+public class SymmetricEncIntegrityPacket
+ extends InputStreamPacket
+{
+ int version;
+
+ SymmetricEncIntegrityPacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ super(in);
+
+ version = in.read();
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyAlgorithmTags.java b/pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyAlgorithmTags.java
new file mode 100644
index 00000000..92fcf10a
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyAlgorithmTags.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.bcpg;
+
+/**
+ * Basic tags for symmetric key algorithms
+ */
+public interface SymmetricKeyAlgorithmTags
+{
+ public static final int NULL = 0; // Plaintext or unencrypted data
+ public static final int IDEA = 1; // IDEA [IDEA]
+ public static final int TRIPLE_DES = 2; // Triple-DES (DES-EDE, as per spec -168 bit key derived from 192)
+ public static final int CAST5 = 3; // CAST5 (128 bit key, as per RFC 2144)
+ public static final int BLOWFISH = 4; // Blowfish (128 bit key, 16 rounds) [BLOWFISH]
+ public static final int SAFER = 5; // SAFER-SK128 (13 rounds) [SAFER]
+ public static final int DES = 6; // Reserved for DES/SK
+ public static final int AES_128 = 7; // Reserved for AES with 128-bit key
+ public static final int AES_192 = 8; // Reserved for AES with 192-bit key
+ public static final int AES_256 = 9; // Reserved for AES with 256-bit key
+ public static final int TWOFISH = 10; // Reserved for Twofish
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java
new file mode 100644
index 00000000..37769fef
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java
@@ -0,0 +1,90 @@
+package org.bouncycastle.bcpg;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Basic type for a symmetric encrypted session key packet
+ */
+public class SymmetricKeyEncSessionPacket
+ extends ContainedPacket
+{
+ private int version;
+ private int encAlgorithm;
+ private S2K s2k;
+ private byte[] secKeyData;
+
+ public SymmetricKeyEncSessionPacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ version = in.read();
+ encAlgorithm = in.read();
+
+ s2k = new S2K(in);
+
+ this.secKeyData = in.readAll();
+ }
+
+ public SymmetricKeyEncSessionPacket(
+ int encAlgorithm,
+ S2K s2k,
+ byte[] secKeyData)
+ {
+ this.version = 4;
+ this.encAlgorithm = encAlgorithm;
+ this.s2k = s2k;
+ this.secKeyData = secKeyData;
+ }
+
+ /**
+ * @return int
+ */
+ public int getEncAlgorithm()
+ {
+ return encAlgorithm;
+ }
+
+ /**
+ * @return S2K
+ */
+ public S2K getS2K()
+ {
+ return s2k;
+ }
+
+ /**
+ * @return byte[]
+ */
+ public byte[] getSecKeyData()
+ {
+ return secKeyData;
+ }
+
+ /**
+ * @return int
+ */
+ public int getVersion()
+ {
+ return version;
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ BCPGOutputStream pOut = new BCPGOutputStream(bOut);
+
+ pOut.write(version);
+ pOut.write(encAlgorithm);
+ pOut.writeObject(s2k);
+
+ if (secKeyData != null && secKeyData.length > 0)
+ {
+ pOut.write(secKeyData);
+ }
+
+ out.writePacket(SYMMETRIC_KEY_ENC_SESSION, bOut.toByteArray(), true);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/TrustPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/TrustPacket.java
new file mode 100644
index 00000000..05a00322
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/TrustPacket.java
@@ -0,0 +1,48 @@
+package org.bouncycastle.bcpg;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Basic type for a trust packet
+ */
+public class TrustPacket
+ extends ContainedPacket
+{
+ byte[] levelAndTrustAmount;
+
+ public TrustPacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ int ch;
+
+ while ((ch = in.read()) >= 0)
+ {
+ bOut.write(ch);
+ }
+
+ levelAndTrustAmount = bOut.toByteArray();
+ }
+
+ public TrustPacket(
+ int trustCode)
+ {
+ this.levelAndTrustAmount = new byte[1];
+
+ this.levelAndTrustAmount[0] = (byte)trustCode;
+ }
+
+ public byte[] getLevelAndTrustAmount()
+ {
+ return levelAndTrustAmount;
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.writePacket(TRUST, levelAndTrustAmount, true);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/UserAttributePacket.java b/pg/src/main/java/org/bouncycastle/bcpg/UserAttributePacket.java
new file mode 100644
index 00000000..3f4913ab
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/UserAttributePacket.java
@@ -0,0 +1,60 @@
+package org.bouncycastle.bcpg;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Vector;
+
+/**
+ * Basic type for a user attribute packet.
+ */
+public class UserAttributePacket
+ extends ContainedPacket
+{
+ private UserAttributeSubpacket[] subpackets;
+
+ public UserAttributePacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ UserAttributeSubpacketInputStream sIn = new UserAttributeSubpacketInputStream(in);
+ UserAttributeSubpacket sub;
+
+ Vector v= new Vector();
+ while ((sub = sIn.readPacket()) != null)
+ {
+ v.addElement(sub);
+ }
+
+ subpackets = new UserAttributeSubpacket[v.size()];
+
+ for (int i = 0; i != subpackets.length; i++)
+ {
+ subpackets[i] = (UserAttributeSubpacket)v.elementAt(i);
+ }
+ }
+
+ public UserAttributePacket(
+ UserAttributeSubpacket[] subpackets)
+ {
+ this.subpackets = subpackets;
+ }
+
+ public UserAttributeSubpacket[] getSubpackets()
+ {
+ return subpackets;
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ for (int i = 0; i != subpackets.length; i++)
+ {
+ subpackets[i].encode(bOut);
+ }
+
+ out.writePacket(USER_ATTRIBUTE, bOut.toByteArray(), false);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/UserAttributeSubpacket.java b/pg/src/main/java/org/bouncycastle/bcpg/UserAttributeSubpacket.java
new file mode 100644
index 00000000..e1db6d0d
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/UserAttributeSubpacket.java
@@ -0,0 +1,91 @@
+package org.bouncycastle.bcpg;
+
+import org.bouncycastle.util.Arrays;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Basic type for a user attribute sub-packet.
+ */
+public class UserAttributeSubpacket
+{
+ int type;
+
+ protected byte[] data;
+
+ protected UserAttributeSubpacket(
+ int type,
+ byte[] data)
+ {
+ this.type = type;
+ this.data = data;
+ }
+
+ public int getType()
+ {
+ return type;
+ }
+
+ /**
+ * return the generic data making up the packet.
+ */
+ public byte[] getData()
+ {
+ return data;
+ }
+
+ public void encode(
+ OutputStream out)
+ throws IOException
+ {
+ int bodyLen = data.length + 1;
+
+ if (bodyLen < 192)
+ {
+ out.write((byte)bodyLen);
+ }
+ else if (bodyLen <= 8383)
+ {
+ bodyLen -= 192;
+
+ out.write((byte)(((bodyLen >> 8) & 0xff) + 192));
+ out.write((byte)bodyLen);
+ }
+ else
+ {
+ out.write(0xff);
+ out.write((byte)(bodyLen >> 24));
+ out.write((byte)(bodyLen >> 16));
+ out.write((byte)(bodyLen >> 8));
+ out.write((byte)bodyLen);
+ }
+
+ out.write(type);
+ out.write(data);
+ }
+
+ public boolean equals(
+ Object o)
+ {
+ if (o == this)
+ {
+ return true;
+ }
+
+ if (!(o instanceof UserAttributeSubpacket))
+ {
+ return false;
+ }
+
+ UserAttributeSubpacket other = (UserAttributeSubpacket)o;
+
+ return this.type == other.type
+ && Arrays.areEqual(this.data, other.data);
+ }
+
+ public int hashCode()
+ {
+ return type ^ Arrays.hashCode(data);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/UserAttributeSubpacketInputStream.java b/pg/src/main/java/org/bouncycastle/bcpg/UserAttributeSubpacketInputStream.java
new file mode 100644
index 00000000..0cce88d2
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/UserAttributeSubpacketInputStream.java
@@ -0,0 +1,116 @@
+package org.bouncycastle.bcpg;
+
+import java.io.*;
+
+import org.bouncycastle.bcpg.attr.ImageAttribute;
+
+/**
+ * reader for user attribute sub-packets
+ */
+public class UserAttributeSubpacketInputStream
+ extends InputStream implements UserAttributeSubpacketTags
+{
+ InputStream in;
+
+ public UserAttributeSubpacketInputStream(
+ InputStream in)
+ {
+ this.in = in;
+ }
+
+ public int available()
+ throws IOException
+ {
+ return in.available();
+ }
+
+ public int read()
+ throws IOException
+ {
+ return in.read();
+ }
+
+ private void readFully(
+ byte[] buf,
+ int off,
+ int len)
+ throws IOException
+ {
+ if (len > 0)
+ {
+ int b = this.read();
+
+ if (b < 0)
+ {
+ throw new EOFException();
+ }
+
+ buf[off] = (byte)b;
+ off++;
+ len--;
+ }
+
+ while (len > 0)
+ {
+ int l = in.read(buf, off, len);
+
+ if (l < 0)
+ {
+ throw new EOFException();
+ }
+
+ off += l;
+ len -= l;
+ }
+ }
+
+ public UserAttributeSubpacket readPacket()
+ throws IOException
+ {
+ int l = this.read();
+ int bodyLen = 0;
+
+ if (l < 0)
+ {
+ return null;
+ }
+
+ if (l < 192)
+ {
+ bodyLen = l;
+ }
+ else if (l <= 223)
+ {
+ bodyLen = ((l - 192) << 8) + (in.read()) + 192;
+ }
+ else if (l == 255)
+ {
+ bodyLen = (in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read();
+ }
+ else
+ {
+ // TODO Error?
+ }
+
+ int tag = in.read();
+
+ if (tag < 0)
+ {
+ throw new EOFException("unexpected EOF reading user attribute sub packet");
+ }
+
+ byte[] data = new byte[bodyLen - 1];
+
+ this.readFully(data, 0, data.length);
+
+ int type = tag;
+
+ switch (type)
+ {
+ case IMAGE_ATTRIBUTE:
+ return new ImageAttribute(data);
+ }
+
+ return new UserAttributeSubpacket(type, data);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/UserAttributeSubpacketTags.java b/pg/src/main/java/org/bouncycastle/bcpg/UserAttributeSubpacketTags.java
new file mode 100644
index 00000000..f6675d86
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/UserAttributeSubpacketTags.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.bcpg;
+
+/**
+ * Basic PGP user attribute sub-packet tag types.
+ */
+public interface UserAttributeSubpacketTags
+{
+ public static final int IMAGE_ATTRIBUTE = 1;
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/UserIDPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/UserIDPacket.java
new file mode 100644
index 00000000..15ae71b1
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/UserIDPacket.java
@@ -0,0 +1,39 @@
+package org.bouncycastle.bcpg;
+
+import java.io.IOException;
+
+import org.bouncycastle.util.Strings;
+
+/**
+ * Basic type for a user ID packet.
+ */
+public class UserIDPacket
+ extends ContainedPacket
+{
+ private byte[] idData;
+
+ public UserIDPacket(
+ BCPGInputStream in)
+ throws IOException
+ {
+ this.idData = in.readAll();
+ }
+
+ public UserIDPacket(
+ String id)
+ {
+ this.idData = Strings.toUTF8ByteArray(id);
+ }
+
+ public String getID()
+ {
+ return Strings.fromUTF8ByteArray(idData);
+ }
+
+ public void encode(
+ BCPGOutputStream out)
+ throws IOException
+ {
+ out.writePacket(USER_ID, idData, true);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/attr/ImageAttribute.java b/pg/src/main/java/org/bouncycastle/bcpg/attr/ImageAttribute.java
new file mode 100644
index 00000000..42d3f865
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/attr/ImageAttribute.java
@@ -0,0 +1,77 @@
+package org.bouncycastle.bcpg.attr;
+
+import org.bouncycastle.bcpg.UserAttributeSubpacket;
+import org.bouncycastle.bcpg.UserAttributeSubpacketTags;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Basic type for a image attribute packet.
+ */
+public class ImageAttribute
+ extends UserAttributeSubpacket
+{
+ public static final int JPEG = 1;
+
+ private static final byte[] ZEROES = new byte[12];
+
+ private int hdrLength;
+ private int version;
+ private int encoding;
+ private byte[] imageData;
+
+ public ImageAttribute(
+ byte[] data)
+ {
+ super(UserAttributeSubpacketTags.IMAGE_ATTRIBUTE, data);
+
+ hdrLength = ((data[1] & 0xff) << 8) | (data[0] & 0xff);
+ version = data[2] & 0xff;
+ encoding = data[3] & 0xff;
+
+ imageData = new byte[data.length - hdrLength];
+ System.arraycopy(data, hdrLength, imageData, 0, imageData.length);
+ }
+
+ public ImageAttribute(
+ int imageType,
+ byte[] imageData)
+ {
+ this(toByteArray(imageType, imageData));
+ }
+
+ private static byte[] toByteArray(int imageType, byte[] imageData)
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ bOut.write(0x10); bOut.write(0x00); bOut.write(0x01);
+ bOut.write(imageType);
+ bOut.write(ZEROES);
+ bOut.write(imageData);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("unable to encode to byte array!");
+ }
+
+ return bOut.toByteArray();
+ }
+
+ public int version()
+ {
+ return version;
+ }
+
+ public int getEncoding()
+ {
+ return encoding;
+ }
+
+ public byte[] getImageData()
+ {
+ return imageData;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/sig/EmbeddedSignature.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/EmbeddedSignature.java
new file mode 100644
index 00000000..1288389f
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/sig/EmbeddedSignature.java
@@ -0,0 +1,18 @@
+package org.bouncycastle.bcpg.sig;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+
+/**
+ * Packet embedded signature
+ */
+public class EmbeddedSignature
+ extends SignatureSubpacket
+{
+ public EmbeddedSignature(
+ boolean critical,
+ byte[] data)
+ {
+ super(SignatureSubpacketTags.EMBEDDED_SIGNATURE, critical, data);
+ }
+} \ No newline at end of file
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/sig/Exportable.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/Exportable.java
new file mode 100644
index 00000000..8adafba5
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/sig/Exportable.java
@@ -0,0 +1,46 @@
+package org.bouncycastle.bcpg.sig;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+
+/**
+ * packet giving signature creation time.
+ */
+public class Exportable
+ extends SignatureSubpacket
+{
+ private static byte[] booleanToByteArray(
+ boolean value)
+ {
+ byte[] data = new byte[1];
+
+ if (value)
+ {
+ data[0] = 1;
+ return data;
+ }
+ else
+ {
+ return data;
+ }
+ }
+
+ public Exportable(
+ boolean critical,
+ byte[] data)
+ {
+ super(SignatureSubpacketTags.EXPORTABLE, critical, data);
+ }
+
+ public Exportable(
+ boolean critical,
+ boolean isExportable)
+ {
+ super(SignatureSubpacketTags.EXPORTABLE, critical, booleanToByteArray(isExportable));
+ }
+
+ public boolean isExportable()
+ {
+ return data[0] != 0;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/sig/Features.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/Features.java
new file mode 100644
index 00000000..1adf2643
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/bcpg/sig/IssuerKeyID.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/IssuerKeyID.java
new file mode 100644
index 00000000..2b908cd3
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/sig/IssuerKeyID.java
@@ -0,0 +1,50 @@
+package org.bouncycastle.bcpg.sig;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+
+/**
+ * packet giving signature creation time.
+ */
+public class IssuerKeyID
+ extends SignatureSubpacket
+{
+ protected static byte[] keyIDToBytes(
+ long keyId)
+ {
+ byte[] data = new byte[8];
+
+ data[0] = (byte)(keyId >> 56);
+ data[1] = (byte)(keyId >> 48);
+ data[2] = (byte)(keyId >> 40);
+ data[3] = (byte)(keyId >> 32);
+ data[4] = (byte)(keyId >> 24);
+ data[5] = (byte)(keyId >> 16);
+ data[6] = (byte)(keyId >> 8);
+ data[7] = (byte)keyId;
+
+ return data;
+ }
+
+ public IssuerKeyID(
+ boolean critical,
+ byte[] data)
+ {
+ super(SignatureSubpacketTags.ISSUER_KEY_ID, critical, data);
+ }
+
+ public IssuerKeyID(
+ boolean critical,
+ long keyID)
+ {
+ super(SignatureSubpacketTags.ISSUER_KEY_ID, critical, keyIDToBytes(keyID));
+ }
+
+ public long getKeyID()
+ {
+ long keyID = ((long)(data[0] & 0xff) << 56) | ((long)(data[1] & 0xff) << 48) | ((long)(data[2] & 0xff) << 40) | ((long)(data[3] & 0xff) << 32)
+ | ((long)(data[4] & 0xff) << 24) | ((data[5] & 0xff) << 16) | ((data[6] & 0xff) << 8) | (data[7] & 0xff);
+
+ return keyID;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/sig/KeyExpirationTime.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/KeyExpirationTime.java
new file mode 100644
index 00000000..91b21b07
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/sig/KeyExpirationTime.java
@@ -0,0 +1,50 @@
+package org.bouncycastle.bcpg.sig;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+
+/**
+ * packet giving time after creation at which the key expires.
+ */
+public class KeyExpirationTime
+ extends SignatureSubpacket
+{
+ protected static byte[] timeToBytes(
+ long t)
+ {
+ byte[] data = new byte[4];
+
+ data[0] = (byte)(t >> 24);
+ data[1] = (byte)(t >> 16);
+ data[2] = (byte)(t >> 8);
+ data[3] = (byte)t;
+
+ return data;
+ }
+
+ public KeyExpirationTime(
+ boolean critical,
+ byte[] data)
+ {
+ super(SignatureSubpacketTags.KEY_EXPIRE_TIME, critical, data);
+ }
+
+ public KeyExpirationTime(
+ boolean critical,
+ long seconds)
+ {
+ super(SignatureSubpacketTags.KEY_EXPIRE_TIME, critical, timeToBytes(seconds));
+ }
+
+ /**
+ * Return the number of seconds after creation time a key is valid for.
+ *
+ * @return second count for key validity.
+ */
+ public long getTime()
+ {
+ long time = ((long)(data[0] & 0xff) << 24) | ((data[1] & 0xff) << 16) | ((data[2] & 0xff) << 8) | (data[3] & 0xff);
+
+ return time;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/sig/KeyFlags.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/KeyFlags.java
new file mode 100644
index 00000000..326f9282
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/sig/KeyFlags.java
@@ -0,0 +1,73 @@
+package org.bouncycastle.bcpg.sig;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+
+/**
+ * Packet holding the key flag values.
+ */
+public class KeyFlags
+ extends SignatureSubpacket
+{
+ public static final int CERTIFY_OTHER = 0x01;
+ public static final int SIGN_DATA = 0x02;
+ public static final int ENCRYPT_COMMS = 0x04;
+ public static final int ENCRYPT_STORAGE = 0x08;
+ public static final int SPLIT = 0x10;
+ public static final int AUTHENTICATION = 0x20;
+ public static final int SHARED = 0x80;
+
+ private static byte[] intToByteArray(
+ int v)
+ {
+ byte[] tmp = new byte[4];
+ int size = 0;
+
+ for (int i = 0; i != 4; i++)
+ {
+ tmp[i] = (byte)(v >> (i * 8));
+ if (tmp[i] != 0)
+ {
+ size = i;
+ }
+ }
+
+ byte[] data = new byte[size + 1];
+
+ System.arraycopy(tmp, 0, data, 0, data.length);
+
+ return data;
+ }
+
+ public KeyFlags(
+ boolean critical,
+ byte[] data)
+ {
+ super(SignatureSubpacketTags.KEY_FLAGS, critical, data);
+ }
+
+ public KeyFlags(
+ boolean critical,
+ int flags)
+ {
+ super(SignatureSubpacketTags.KEY_FLAGS, critical, intToByteArray(flags));
+ }
+
+ /**
+ * Return the flag values contained in the first 4 octets (note: at the moment
+ * the standard only uses the first one).
+ *
+ * @return flag values.
+ */
+ public int getFlags()
+ {
+ int flags = 0;
+
+ for (int i = 0; i != data.length; i++)
+ {
+ flags |= (data[i] & 0xff) << (i * 8);
+ }
+
+ return flags;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/sig/NotationData.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/NotationData.java
new file mode 100644
index 00000000..d2a0d19d
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/sig/NotationData.java
@@ -0,0 +1,104 @@
+package org.bouncycastle.bcpg.sig;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+import org.bouncycastle.util.Strings;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Class provided a NotationData object according to
+ * RFC2440, Chapter 5.2.3.15. Notation Data
+ */
+public class NotationData
+ extends SignatureSubpacket
+{
+ public static final int HEADER_FLAG_LENGTH = 4;
+ public static final int HEADER_NAME_LENGTH = 2;
+ public static final int HEADER_VALUE_LENGTH = 2;
+
+ public NotationData(boolean critical, byte[] data)
+ {
+ super(SignatureSubpacketTags.NOTATION_DATA, critical, data);
+ }
+
+ public NotationData(
+ boolean critical,
+ boolean humanReadable,
+ String notationName,
+ String notationValue)
+ {
+ super(SignatureSubpacketTags.NOTATION_DATA, critical, createData(humanReadable, notationName, notationValue));
+ }
+
+ private static byte[] createData(boolean humanReadable, String notationName, String notationValue)
+ {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+// (4 octets of flags, 2 octets of name length (M),
+// 2 octets of value length (N),
+// M octets of name data,
+// N octets of value data)
+
+ // flags
+ out.write(humanReadable ? 0x80 : 0x00);
+ out.write(0x0);
+ out.write(0x0);
+ out.write(0x0);
+
+ byte[] nameData, valueData = null;
+ int nameLength, valueLength;
+
+ nameData = Strings.toUTF8ByteArray(notationName);
+ nameLength = Math.min(nameData.length, 0xFF);
+
+ valueData = Strings.toUTF8ByteArray(notationValue);
+ valueLength = Math.min(valueData.length, 0xFF);
+
+ // name length
+ out.write((nameLength >>> 8) & 0xFF);
+ out.write((nameLength >>> 0) & 0xFF);
+
+ // value length
+ out.write((valueLength >>> 8) & 0xFF);
+ out.write((valueLength >>> 0) & 0xFF);
+
+ // name
+ out.write(nameData, 0, nameLength);
+
+ // value
+ out.write(valueData, 0, valueLength);
+
+ return out.toByteArray();
+ }
+
+ public boolean isHumanReadable()
+ {
+ return data[0] == (byte)0x80;
+ }
+
+ public String getNotationName()
+ {
+ int nameLength = ((data[HEADER_FLAG_LENGTH] << 8) + (data[HEADER_FLAG_LENGTH + 1] << 0));
+
+ byte bName[] = new byte[nameLength];
+ System.arraycopy(data, HEADER_FLAG_LENGTH + HEADER_NAME_LENGTH + HEADER_VALUE_LENGTH, bName, 0, nameLength);
+
+ return Strings.fromUTF8ByteArray(bName);
+ }
+
+ public String getNotationValue()
+ {
+ return Strings.fromUTF8ByteArray(getNotationValueBytes());
+ }
+
+ public byte[] getNotationValueBytes()
+ {
+ int nameLength = ((data[HEADER_FLAG_LENGTH] << 8) + (data[HEADER_FLAG_LENGTH + 1] << 0));
+ int valueLength = ((data[HEADER_FLAG_LENGTH + HEADER_NAME_LENGTH] << 8) + (data[HEADER_FLAG_LENGTH + HEADER_NAME_LENGTH + 1] << 0));
+
+ byte bValue[] = new byte[valueLength];
+ System.arraycopy(data, HEADER_FLAG_LENGTH + HEADER_NAME_LENGTH + HEADER_VALUE_LENGTH + nameLength, bValue, 0, valueLength);
+ return bValue;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/sig/PreferredAlgorithms.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/PreferredAlgorithms.java
new file mode 100644
index 00000000..ca83b056
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/sig/PreferredAlgorithms.java
@@ -0,0 +1,59 @@
+package org.bouncycastle.bcpg.sig;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+
+/**
+ * packet giving signature creation time.
+ */
+public class PreferredAlgorithms
+ extends SignatureSubpacket
+{
+ private static byte[] intToByteArray(
+ int[] v)
+ {
+ byte[] data = new byte[v.length];
+
+ for (int i = 0; i != v.length; i++)
+ {
+ data[i] = (byte)v[i];
+ }
+
+ return data;
+ }
+
+ public PreferredAlgorithms(
+ int type,
+ boolean critical,
+ byte[] data)
+ {
+ super(type, critical, data);
+ }
+
+ public PreferredAlgorithms(
+ int type,
+ boolean critical,
+ int[] preferrences)
+ {
+ super(type, critical, intToByteArray(preferrences));
+ }
+
+ /**
+ * @deprecated mispelt!
+ */
+ public int[] getPreferrences()
+ {
+ return getPreferences();
+ }
+
+ public int[] getPreferences()
+ {
+ int[] v = new int[data.length];
+
+ for (int i = 0; i != v.length; i++)
+ {
+ v[i] = data[i] & 0xff;
+ }
+
+ return v;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/sig/PrimaryUserID.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/PrimaryUserID.java
new file mode 100644
index 00000000..2e0d7fd3
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/sig/PrimaryUserID.java
@@ -0,0 +1,46 @@
+package org.bouncycastle.bcpg.sig;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+
+/**
+ * packet giving whether or not the signature is signed using the primary user ID for the key.
+ */
+public class PrimaryUserID
+ extends SignatureSubpacket
+{
+ private static byte[] booleanToByteArray(
+ boolean value)
+ {
+ byte[] data = new byte[1];
+
+ if (value)
+ {
+ data[0] = 1;
+ return data;
+ }
+ else
+ {
+ return data;
+ }
+ }
+
+ public PrimaryUserID(
+ boolean critical,
+ byte[] data)
+ {
+ super(SignatureSubpacketTags.PRIMARY_USER_ID, critical, data);
+ }
+
+ public PrimaryUserID(
+ boolean critical,
+ boolean isPrimaryUserID)
+ {
+ super(SignatureSubpacketTags.PRIMARY_USER_ID, critical, booleanToByteArray(isPrimaryUserID));
+ }
+
+ public boolean isPrimaryUserID()
+ {
+ return data[0] != 0;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/sig/Revocable.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/Revocable.java
new file mode 100644
index 00000000..9807ca89
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/sig/Revocable.java
@@ -0,0 +1,46 @@
+package org.bouncycastle.bcpg.sig;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+
+/**
+ * packet giving whether or not is revocable.
+ */
+public class Revocable
+ extends SignatureSubpacket
+{
+ private static byte[] booleanToByteArray(
+ boolean value)
+ {
+ byte[] data = new byte[1];
+
+ if (value)
+ {
+ data[0] = 1;
+ return data;
+ }
+ else
+ {
+ return data;
+ }
+ }
+
+ public Revocable(
+ boolean critical,
+ byte[] data)
+ {
+ super(SignatureSubpacketTags.REVOCABLE, critical, data);
+ }
+
+ public Revocable(
+ boolean critical,
+ boolean isRevocable)
+ {
+ super(SignatureSubpacketTags.REVOCABLE, critical, booleanToByteArray(isRevocable));
+ }
+
+ public boolean isRevocable()
+ {
+ return data[0] != 0;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationKey.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationKey.java
new file mode 100644
index 00000000..b46eab56
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationKeyTags.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationKeyTags.java
new file mode 100644
index 00000000..aee90c1e
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationReason.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationReason.java
new file mode 100644
index 00000000..e3b50fec
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationReasonTags.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationReasonTags.java
new file mode 100644
index 00000000..576b1818
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/bcpg/sig/SignatureCreationTime.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/SignatureCreationTime.java
new file mode 100644
index 00000000..51705013
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/sig/SignatureCreationTime.java
@@ -0,0 +1,48 @@
+package org.bouncycastle.bcpg.sig;
+
+import java.util.Date;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+
+/**
+ * packet giving signature creation time.
+ */
+public class SignatureCreationTime
+ extends SignatureSubpacket
+{
+ protected static byte[] timeToBytes(
+ Date date)
+ {
+ byte[] data = new byte[4];
+ long t = date.getTime() / 1000;
+
+ data[0] = (byte)(t >> 24);
+ data[1] = (byte)(t >> 16);
+ data[2] = (byte)(t >> 8);
+ data[3] = (byte)t;
+
+ return data;
+ }
+
+ public SignatureCreationTime(
+ boolean critical,
+ byte[] data)
+ {
+ super(SignatureSubpacketTags.CREATION_TIME, critical, data);
+ }
+
+ public SignatureCreationTime(
+ boolean critical,
+ Date date)
+ {
+ super(SignatureSubpacketTags.CREATION_TIME, critical, timeToBytes(date));
+ }
+
+ public Date getTime()
+ {
+ long time = ((long)(data[0] & 0xff) << 24) | ((data[1] & 0xff) << 16) | ((data[2] & 0xff) << 8) | (data[3] & 0xff);
+
+ return new Date(time * 1000);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/sig/SignatureExpirationTime.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/SignatureExpirationTime.java
new file mode 100644
index 00000000..6aa949c5
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/sig/SignatureExpirationTime.java
@@ -0,0 +1,48 @@
+package org.bouncycastle.bcpg.sig;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+
+/**
+ * packet giving signature expiration time.
+ */
+public class SignatureExpirationTime
+ extends SignatureSubpacket
+{
+ protected static byte[] timeToBytes(
+ long t)
+ {
+ byte[] data = new byte[4];
+
+ data[0] = (byte)(t >> 24);
+ data[1] = (byte)(t >> 16);
+ data[2] = (byte)(t >> 8);
+ data[3] = (byte)t;
+
+ return data;
+ }
+
+ public SignatureExpirationTime(
+ boolean critical,
+ byte[] data)
+ {
+ super(SignatureSubpacketTags.EXPIRE_TIME, critical, data);
+ }
+
+ public SignatureExpirationTime(
+ boolean critical,
+ long seconds)
+ {
+ super(SignatureSubpacketTags.EXPIRE_TIME, critical, timeToBytes(seconds));
+ }
+
+ /**
+ * return time in seconds before signature expires after creation time.
+ */
+ public long getTime()
+ {
+ long time = ((long)(data[0] & 0xff) << 24) | ((data[1] & 0xff) << 16) | ((data[2] & 0xff) << 8) | (data[3] & 0xff);
+
+ return time;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/sig/SignerUserID.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/SignerUserID.java
new file mode 100644
index 00000000..90941aa8
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/sig/SignerUserID.java
@@ -0,0 +1,50 @@
+package org.bouncycastle.bcpg.sig;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+
+/**
+ * packet giving the User ID of the signer.
+ */
+public class SignerUserID
+ extends SignatureSubpacket
+{
+ private static byte[] userIDToBytes(
+ String id)
+ {
+ byte[] idData = new byte[id.length()];
+
+ for (int i = 0; i != id.length(); i++)
+ {
+ idData[i] = (byte)id.charAt(i);
+ }
+
+ return idData;
+ }
+
+ public SignerUserID(
+ boolean critical,
+ byte[] data)
+ {
+ super(SignatureSubpacketTags.SIGNER_USER_ID, critical, data);
+ }
+
+ public SignerUserID(
+ boolean critical,
+ String userID)
+ {
+ super(SignatureSubpacketTags.SIGNER_USER_ID, critical, userIDToBytes(userID));
+ }
+
+ public String getID()
+ {
+ char[] chars = new char[data.length];
+
+ for (int i = 0; i != chars.length; i++)
+ {
+ chars[i] = (char)(data[i] & 0xff);
+ }
+
+ return new String(chars);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/bcpg/sig/TrustSignature.java b/pg/src/main/java/org/bouncycastle/bcpg/sig/TrustSignature.java
new file mode 100644
index 00000000..a3239003
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/bcpg/sig/TrustSignature.java
@@ -0,0 +1,48 @@
+package org.bouncycastle.bcpg.sig;
+
+import org.bouncycastle.bcpg.SignatureSubpacket;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+
+/**
+ * packet giving trust.
+ */
+public class TrustSignature
+ extends SignatureSubpacket
+{
+ private static byte[] intToByteArray(
+ int v1,
+ int v2)
+ {
+ byte[] data = new byte[2];
+
+ data[0] = (byte)v1;
+ data[1] = (byte)v2;
+
+ return data;
+ }
+
+ public TrustSignature(
+ boolean critical,
+ byte[] data)
+ {
+ super(SignatureSubpacketTags.TRUST_SIG, critical, data);
+ }
+
+ public TrustSignature(
+ boolean critical,
+ int depth,
+ int trustAmount)
+ {
+ super(SignatureSubpacketTags.TRUST_SIG, critical, intToByteArray(depth, trustAmount));
+ }
+
+ public int getDepth()
+ {
+ return data[0] & 0xff;
+ }
+
+ public int getTrustAmount()
+ {
+ return data[1] & 0xff;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPCompressedData.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPCompressedData.java
new file mode 100644
index 00000000..4f13a98f
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPCompressedData.java
@@ -0,0 +1,143 @@
+package org.bouncycastle.openpgp;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.CompressedDataPacket;
+import org.bouncycastle.bcpg.CompressionAlgorithmTags;
+import org.bouncycastle.apache.bzip2.CBZip2InputStream;
+
+/**
+ * Compressed data objects.
+ */
+public class PGPCompressedData
+ implements CompressionAlgorithmTags
+{
+ CompressedDataPacket data;
+
+ public PGPCompressedData(
+ BCPGInputStream pIn)
+ throws IOException
+ {
+ data = (CompressedDataPacket)pIn.readPacket();
+ }
+
+ /**
+ * Return the algorithm used for compression
+ *
+ * @return algorithm code
+ */
+ public int getAlgorithm()
+ {
+ return data.getAlgorithm();
+ }
+
+ /**
+ * Return the raw input stream contained in the object.
+ *
+ * @return InputStream
+ */
+ public InputStream getInputStream()
+ {
+ return data.getInputStream();
+ }
+
+ /**
+ * Return an uncompressed input stream which allows reading of the
+ * compressed data.
+ *
+ * @return InputStream
+ * @throws PGPException
+ */
+ public InputStream getDataStream()
+ throws PGPException
+ {
+ if (this.getAlgorithm() == UNCOMPRESSED)
+ {
+ return this.getInputStream();
+ }
+ if (this.getAlgorithm() == ZIP)
+ {
+ return new InflaterInputStream(this.getInputStream(), new Inflater(true))
+ {
+ // If the "nowrap" inflater option is used the stream can
+ // apparently overread - we override fill() and provide
+ // an extra byte for the end of the input stream to get
+ // around this.
+ //
+ // Totally weird...
+ //
+ protected void fill() throws IOException
+ {
+ if (eof)
+ {
+ throw new EOFException("Unexpected end of ZIP input stream");
+ }
+
+ len = this.in.read(buf, 0, buf.length);
+
+ if (len == -1)
+ {
+ buf[0] = 0;
+ len = 1;
+ eof = true;
+ }
+
+ inf.setInput(buf, 0, len);
+ }
+
+ private boolean eof = false;
+ };
+ }
+ if (this.getAlgorithm() == ZLIB)
+ {
+ return new InflaterInputStream(this.getInputStream())
+ {
+ // If the "nowrap" inflater option is used the stream can
+ // apparently overread - we override fill() and provide
+ // an extra byte for the end of the input stream to get
+ // around this.
+ //
+ // Totally weird...
+ //
+ protected void fill() throws IOException
+ {
+ if (eof)
+ {
+ throw new EOFException("Unexpected end of ZIP input stream");
+ }
+
+ len = this.in.read(buf, 0, buf.length);
+
+ if (len == -1)
+ {
+ buf[0] = 0;
+ len = 1;
+ eof = true;
+ }
+
+ inf.setInput(buf, 0, len);
+ }
+
+ private boolean eof = false;
+ };
+ }
+ if (this.getAlgorithm() == BZIP2)
+ {
+ try
+ {
+ return new CBZip2InputStream(this.getInputStream());
+ }
+ catch (IOException e)
+ {
+ throw new PGPException("I/O problem with stream: " + e, e);
+ }
+ }
+
+ throw new PGPException("can't recognise compression algorithm: " + this.getAlgorithm());
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPCompressedDataGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPCompressedDataGenerator.java
new file mode 100644
index 00000000..4a3ebaf6
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPCompressedDataGenerator.java
@@ -0,0 +1,201 @@
+package org.bouncycastle.openpgp;
+
+import org.bouncycastle.apache.bzip2.CBZip2OutputStream;
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.CompressionAlgorithmTags;
+import org.bouncycastle.bcpg.PacketTags;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+/**
+ *class for producing compressed data packets.
+ */
+public class PGPCompressedDataGenerator
+ implements CompressionAlgorithmTags, StreamGenerator
+{
+ private int algorithm;
+ private int compression;
+
+ private OutputStream dOut;
+ private BCPGOutputStream pkOut;
+
+ public PGPCompressedDataGenerator(
+ int algorithm)
+ {
+ this(algorithm, Deflater.DEFAULT_COMPRESSION);
+ }
+
+ public PGPCompressedDataGenerator(
+ int algorithm,
+ int compression)
+ {
+ switch (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 < 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
+ * the compressed object.
+ * <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 underlying OutputStream to be used.
+ * @return OutputStream
+ * @throws IOException, IllegalStateException
+ */
+ public OutputStream open(
+ OutputStream out)
+ throws IOException
+ {
+ if (dOut != null)
+ {
+ throw new IllegalStateException("generator already in open state");
+ }
+
+ this.pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA);
+
+ doOpen();
+
+ return new WrappedGeneratorStream(dOut, this);
+ }
+
+ /**
+ * 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>
+ * 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.
+ * </p>
+ * <p>
+ * <b>Note</b>: using this may break compatibility with RFC 1991 compliant tools. Only recent OpenPGP
+ * implementations are capable of accepting these streams.
+ * </p>
+ *
+ * @param out underlying OutputStream to be used.
+ * @param buffer the buffer to use.
+ * @return OutputStream
+ * @throws IOException
+ * @throws PGPException
+ */
+ public OutputStream open(
+ OutputStream out,
+ byte[] buffer)
+ throws IOException, PGPException
+ {
+ if (dOut != null)
+ {
+ throw new IllegalStateException("generator already in open state");
+ }
+
+ 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 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();
+ }
+ }
+
+ /**
+ * Close the compressed object - this is equivalent to calling close on the stream
+ * returned by the open() method.
+ *
+ * @throws IOException
+ */
+ public void close()
+ throws IOException
+ {
+ if (dOut != null)
+ {
+ if (dOut != pkOut)
+ {
+ dOut.close();
+ dOut.flush();
+ }
+
+ dOut = null;
+
+ pkOut.finish();
+ pkOut.flush();
+ pkOut = 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/pg/src/main/java/org/bouncycastle/openpgp/PGPDataValidationException.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPDataValidationException.java
new file mode 100644
index 00000000..d48b6996
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPDataValidationException.java
@@ -0,0 +1,17 @@
+package org.bouncycastle.openpgp;
+
+/**
+ * Thrown if the iv at the start of a data stream indicates the wrong key
+ * is being used.
+ */
+public class PGPDataValidationException
+ extends PGPException
+{
+ /**
+ * @param message
+ */
+ public PGPDataValidationException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedData.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedData.java
new file mode 100644
index 00000000..cf01ece2
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedData.java
@@ -0,0 +1,147 @@
+package org.bouncycastle.openpgp;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+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
+{
+ protected class TruncatedStream extends InputStream
+ {
+ int[] lookAhead = new int[22];
+ int bufPtr;
+ InputStream in;
+
+ TruncatedStream(
+ InputStream in)
+ throws IOException
+ {
+ for (int i = 0; i != lookAhead.length; i++)
+ {
+ if ((lookAhead[i] = in.read()) < 0)
+ {
+ throw new EOFException();
+ }
+ }
+
+ bufPtr = 0;
+ this.in = in;
+ }
+
+ public int read()
+ throws IOException
+ {
+ int ch = in.read();
+
+ if (ch >= 0)
+ {
+ int c = lookAhead[bufPtr];
+
+ lookAhead[bufPtr] = ch;
+ bufPtr = (bufPtr + 1) % lookAhead.length;
+
+ return c;
+ }
+
+ return -1;
+ }
+
+ int[] getLookAhead()
+ {
+ int[] tmp = new int[lookAhead.length];
+ int count = 0;
+
+ for (int i = bufPtr; i != lookAhead.length; i++)
+ {
+ tmp[count++] = lookAhead[i];
+ }
+ for (int i = 0; i != bufPtr; i++)
+ {
+ tmp[count++] = lookAhead[i];
+ }
+
+ return tmp;
+ }
+ }
+
+ InputStreamPacket encData;
+ InputStream encStream;
+ TruncatedStream truncStream;
+ PGPDigestCalculator integrityCalculator;
+
+ PGPEncryptedData(
+ InputStreamPacket encData)
+ {
+ this.encData = encData;
+ }
+
+ /**
+ * Return the raw input stream for the data stream.
+ *
+ * @return InputStream
+ */
+ public InputStream getInputStream()
+ {
+ return encData.getInputStream();
+ }
+
+ /**
+ * Return true if the message is integrity protected.
+ * @return true if there is a modification detection code package associated with this stream
+ */
+ public boolean isIntegrityProtected()
+ {
+ return (encData instanceof SymmetricEncIntegrityPacket);
+ }
+
+ /**
+ * Note: This can only be called after the message has been read.
+ *
+ * @return true if the message verifies, false otherwise.
+ * @throws PGPException if the message is not integrity protected.
+ */
+ public boolean verify()
+ throws PGPException, IOException
+ {
+ if (!this.isIntegrityProtected())
+ {
+ throw new PGPException("data not integrity protected.");
+ }
+
+ //
+ // make sure we are at the end.
+ //
+ while (encStream.read() >= 0)
+ {
+ // do nothing
+ }
+
+ //
+ // process the MDC packet
+ //
+ int[] lookAhead = truncStream.getLookAhead();
+
+ OutputStream dOut = integrityCalculator.getOutputStream();
+
+ dOut.write((byte)lookAhead[0]);
+ dOut.write((byte)lookAhead[1]);
+
+ byte[] digest = integrityCalculator.getDigest();
+ byte[] streamDigest = new byte[digest.length];
+
+ for (int i = 0; i != streamDigest.length; i++)
+ {
+ streamDigest[i] = (byte)lookAhead[i + 2];
+ }
+
+ return Arrays.constantTimeAreEqual(digest, streamDigest);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java
new file mode 100644
index 00000000..24d35603
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java
@@ -0,0 +1,537 @@
+package org.bouncycastle.openpgp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+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
+{
+ /**
+ * 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;
+
+ private static Provider defProvider;
+
+ /**
+ * 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(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(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
+ * @deprecated use constructor that takes a PGPDataEncryptorBuilder
+ */
+ public PGPEncryptedDataGenerator(
+ int encAlgorithm,
+ boolean withIntegrityPacket,
+ SecureRandom rand,
+ String 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(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 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(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(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 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)
+ {
+ defProvider = new BouncyCastleProvider();
+ }
+
+ 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)
+ throws NoSuchProviderException, PGPException
+ {
+ if (!key.isEncryptionKey())
+ {
+ throw new IllegalArgumentException("passed in key not an encryption key!");
+ }
+
+ if (defProvider == null)
+ {
+ defProvider = new BouncyCastleProvider();
+ }
+
+ 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)
+ {
+ 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/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataList.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataList.java
new file mode 100644
index 00000000..7fd9c254
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataList.java
@@ -0,0 +1,75 @@
+package org.bouncycastle.openpgp;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.InputStreamPacket;
+import org.bouncycastle.bcpg.PacketTags;
+import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
+import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
+
+/**
+ * A holder for a list of PGP encryption method packets.
+ */
+public class PGPEncryptedDataList
+{
+ List list = new ArrayList();
+ InputStreamPacket data;
+
+ public PGPEncryptedDataList(
+ BCPGInputStream pIn)
+ throws IOException
+ {
+ while (pIn.nextPacketTag() == PacketTags.PUBLIC_KEY_ENC_SESSION
+ || pIn.nextPacketTag() == PacketTags.SYMMETRIC_KEY_ENC_SESSION)
+ {
+ list.add(pIn.readPacket());
+ }
+
+ data = (InputStreamPacket)pIn.readPacket();
+
+ for (int i = 0; i != list.size(); i++)
+ {
+ if (list.get(i) instanceof SymmetricKeyEncSessionPacket)
+ {
+ list.set(i, new PGPPBEEncryptedData((SymmetricKeyEncSessionPacket)list.get(i), data));
+ }
+ else
+ {
+ list.set(i, new PGPPublicKeyEncryptedData((PublicKeyEncSessionPacket)list.get(i), data));
+ }
+ }
+ }
+
+ public Object get(
+ int index)
+ {
+ return list.get(index);
+ }
+
+ public int size()
+ {
+ return list.size();
+ }
+
+ public boolean isEmpty()
+ {
+ return list.isEmpty();
+ }
+
+ /**
+ * @deprecated misspelt - use getEncryptedDataObjects()
+ */
+ public Iterator getEncyptedDataObjects()
+ {
+ return list.iterator();
+ }
+
+ public Iterator getEncryptedDataObjects()
+ {
+ return list.iterator();
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPException.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPException.java
new file mode 100644
index 00000000..0420fcd8
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPException.java
@@ -0,0 +1,35 @@
+package org.bouncycastle.openpgp;
+
+/**
+ * generic exception class for PGP encoding/decoding problems
+ */
+public class PGPException
+ extends Exception
+{
+ Exception underlying;
+
+ public PGPException(
+ String message)
+ {
+ super(message);
+ }
+
+ public PGPException(
+ String message,
+ Exception underlying)
+ {
+ super(message);
+ this.underlying = underlying;
+ }
+
+ public Exception getUnderlyingException()
+ {
+ return underlying;
+ }
+
+
+ public Throwable getCause()
+ {
+ return underlying;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyFlags.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyFlags.java
new file mode 100644
index 00000000..6e4a4de6
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyFlags.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.openpgp;
+
+/**
+ * key flag values for the KeyFlags subpacket.
+ */
+public interface PGPKeyFlags
+{
+ public static final int CAN_CERTIFY = 0x01; // This key may be used to certify other keys.
+
+ public static final int CAN_SIGN = 0x02; // This key may be used to sign data.
+
+ public static final int CAN_ENCRYPT_COMMS = 0x04; // This key may be used to encrypt communications.
+
+ public static final int CAN_ENCRYPT_STORAGE = 0x08; // This key may be used to encrypt storage.
+
+ public static final int MAYBE_SPLIT = 0x10; // The private component of this key may have been split by a secret-sharing mechanism.
+
+ public static final int MAYBE_SHARED = 0x80; // The private component of this key may be in the possession of more than one person.
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java
new file mode 100644
index 00000000..856468e4
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java
@@ -0,0 +1,148 @@
+package org.bouncycastle.openpgp;
+
+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.
+ * <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;
+
+ /**
+ * @deprecated use BcPGPKeyPair or JcaPGPKeyPair as appropriate.
+ */
+ public PGPKeyPair(
+ int algorithm,
+ KeyPair keyPair,
+ Date time,
+ String provider)
+ throws PGPException, NoSuchProviderException
+ {
+ this(algorithm, keyPair.getPublic(), keyPair.getPrivate(), time, provider);
+ }
+
+ /**
+ * @deprecated use BcPGPKeyPair or JcaPGPKeyPair as appropriate.
+ */
+ public PGPKeyPair(
+ int algorithm,
+ KeyPair keyPair,
+ Date time)
+ throws PGPException
+ {
+ this(algorithm, keyPair.getPublic(), keyPair.getPrivate(), time);
+ }
+
+ /**
+ * @deprecated use BcPGPKeyPair or JcaPGPKeyPair as appropriate.
+ */
+ public PGPKeyPair(
+ int algorithm,
+ PublicKey pubKey,
+ PrivateKey privKey,
+ Date time,
+ String provider)
+ throws PGPException, NoSuchProviderException
+ {
+ this(algorithm, pubKey, privKey, time);
+ }
+
+ /**
+ * @deprecated use BcPGPKeyPair or JcaPGPKeyPair as appropriate.
+ */
+ public PGPKeyPair(
+ int algorithm,
+ PublicKey pubKey,
+ PrivateKey privKey,
+ Date time)
+ throws PGPException
+ {
+ this.pub = new PGPPublicKey(algorithm, pubKey, time);
+
+ 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);
+ }
+
+ /**
+ * 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;
+ }
+
+ protected PGPKeyPair()
+ {
+ }
+
+ /**
+ * 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/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRing.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRing.java
new file mode 100644
index 00000000..e12da58c
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRing.java
@@ -0,0 +1,125 @@
+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;
+import org.bouncycastle.bcpg.SignaturePacket;
+import org.bouncycastle.bcpg.TrustPacket;
+import org.bouncycastle.bcpg.UserAttributePacket;
+import org.bouncycastle.bcpg.UserIDPacket;
+
+public abstract class PGPKeyRing
+{
+ PGPKeyRing()
+ {
+ }
+
+ static BCPGInputStream wrap(InputStream in)
+ {
+ if (in instanceof BCPGInputStream)
+ {
+ return (BCPGInputStream)in;
+ }
+
+ return new BCPGInputStream(in);
+ }
+
+ static TrustPacket readOptionalTrustPacket(
+ BCPGInputStream pIn)
+ throws IOException
+ {
+ return (pIn.nextPacketTag() == PacketTags.TRUST)
+ ? (TrustPacket) pIn.readPacket()
+ : null;
+ }
+
+ static List readSignaturesAndTrust(
+ BCPGInputStream pIn)
+ throws IOException
+ {
+ try
+ {
+ List sigList = new ArrayList();
+
+ while (pIn.nextPacketTag() == PacketTags.SIGNATURE)
+ {
+ SignaturePacket signaturePacket = (SignaturePacket)pIn.readPacket();
+ TrustPacket trustPacket = readOptionalTrustPacket(pIn);
+
+ sigList.add(new PGPSignature(signaturePacket, trustPacket));
+ }
+
+ return sigList;
+ }
+ catch (PGPException e)
+ {
+ throw new IOException("can't create signature object: " + e.getMessage()
+ + ", cause: " + e.getUnderlyingException().toString());
+ }
+ }
+
+ static void readUserIDs(
+ BCPGInputStream pIn,
+ List ids,
+ List idTrusts,
+ List idSigs)
+ throws IOException
+ {
+ while (pIn.nextPacketTag() == PacketTags.USER_ID
+ || pIn.nextPacketTag() == PacketTags.USER_ATTRIBUTE)
+ {
+ Packet obj = pIn.readPacket();
+ if (obj instanceof UserIDPacket)
+ {
+ UserIDPacket id = (UserIDPacket)obj;
+ ids.add(id.getID());
+ }
+ else
+ {
+ UserAttributePacket user = (UserAttributePacket)obj;
+ ids.add(new PGPUserAttributeSubpacketVector(user.getSubpackets()));
+ }
+
+ idTrusts.add(readOptionalTrustPacket(pIn));
+ 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/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java
new file mode 100644
index 00000000..83c034bf
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java
@@ -0,0 +1,272 @@
+package org.bouncycastle.openpgp;
+
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+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
+ * 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 using old style checksumming. It is recommended to use
+ * SHA1 checksumming where possible.
+ *
+ * @param certificationLevel the certification level for keys on this ring.
+ * @param masterKey the master key pair.
+ * @param id the id to be associated with the ring.
+ * @param encAlgorithm the algorithm to be used to protect secret keys.
+ * @param passPhrase the passPhrase to be used to protect secret keys.
+ * @param hashedPcks packets to be included in the certification hash.
+ * @param unhashedPcks packets to be attached unhashed to the certification.
+ * @param rand input secured random
+ * @param provider the provider to use for encryption.
+ *
+ * @throws PGPException
+ * @throws NoSuchProviderException
+ * @deprecated use method taking PBESecretKeyDecryptor
+ */
+ public PGPKeyRingGenerator(
+ int certificationLevel,
+ PGPKeyPair masterKey,
+ String id,
+ int encAlgorithm,
+ char[] passPhrase,
+ PGPSignatureSubpacketVector hashedPcks,
+ PGPSignatureSubpacketVector unhashedPcks,
+ SecureRandom rand,
+ String provider)
+ throws PGPException, NoSuchProviderException
+ {
+ this(certificationLevel, masterKey, id, encAlgorithm, passPhrase, false, hashedPcks, unhashedPcks, rand, provider);
+ }
+
+ /**
+ * Create a new key ring generator.
+ *
+ * @param certificationLevel the certification level for keys on this ring.
+ * @param masterKey the master key pair.
+ * @param id the id to be associated with the ring.
+ * @param encAlgorithm the algorithm to be used to protect secret keys.
+ * @param passPhrase the passPhrase to be used to protect secret keys.
+ * @param useSHA1 checksum the secret keys with SHA1 rather than the older 16 bit checksum.
+ * @param hashedPcks packets to be included in the certification hash.
+ * @param unhashedPcks packets to be attached unhashed to the certification.
+ * @param rand input secured random
+ * @param provider the provider to use for encryption.
+ *
+ * @throws PGPException
+ * @throws NoSuchProviderException
+ * @deprecated use method taking PBESecretKeyDecryptor
+ */
+ public PGPKeyRingGenerator(
+ int certificationLevel,
+ PGPKeyPair masterKey,
+ String id,
+ int encAlgorithm,
+ char[] passPhrase,
+ boolean useSHA1,
+ PGPSignatureSubpacketVector hashedPcks,
+ PGPSignatureSubpacketVector unhashedPcks,
+ SecureRandom rand,
+ String provider)
+ throws PGPException, NoSuchProviderException
+ {
+ this(certificationLevel, masterKey, id, encAlgorithm, passPhrase, useSHA1, hashedPcks, unhashedPcks, rand, PGPUtil.getProvider(provider));
+ }
+
+ /**
+ * Create a new key ring generator.
+ *
+ * @param certificationLevel the certification level for keys on this ring.
+ * @param masterKey the master key pair.
+ * @param id the id to be associated with the ring.
+ * @param encAlgorithm the algorithm to be used to protect secret keys.
+ * @param passPhrase the passPhrase to be used to protect secret keys.
+ * @param useSHA1 checksum the secret keys with SHA1 rather than the older 16 bit checksum.
+ * @param hashedPcks packets to be included in the certification hash.
+ * @param unhashedPcks packets to be attached unhashed to the certification.
+ * @param rand input secured random
+ * @param provider the provider to use for encryption.
+ *
+ * @throws PGPException
+ * @throws NoSuchProviderException
+ * @deprecated use method taking PBESecretKeyEncryptor
+ */
+ public PGPKeyRingGenerator(
+ int certificationLevel,
+ PGPKeyPair masterKey,
+ String id,
+ int encAlgorithm,
+ char[] passPhrase,
+ boolean useSHA1,
+ PGPSignatureSubpacketVector hashedPcks,
+ PGPSignatureSubpacketVector unhashedPcks,
+ SecureRandom rand,
+ Provider provider)
+ throws PGPException, NoSuchProviderException
+ {
+ this.masterKey = masterKey;
+ this.hashedPcks = hashedPcks;
+ this.unhashedPcks = unhashedPcks;
+ 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, 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));
+ }
+
+ /**
+ * 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);
+ }
+
+ private static PGPDigestCalculator convertSHA1Flag(boolean useSHA1)
+ throws PGPException
+ {
+ return useSHA1 ? new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1) : null;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyValidationException.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyValidationException.java
new file mode 100644
index 00000000..1604a46b
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyValidationException.java
@@ -0,0 +1,16 @@
+package org.bouncycastle.openpgp;
+
+/**
+ * Thrown if the key checksum is invalid.
+ */
+public class PGPKeyValidationException
+ extends PGPException
+{
+ /**
+ * @param message
+ */
+ public PGPKeyValidationException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPLiteralData.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPLiteralData.java
new file mode 100644
index 00000000..b163b86a
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPLiteralData.java
@@ -0,0 +1,96 @@
+package org.bouncycastle.openpgp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Date;
+
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.LiteralDataPacket;
+
+/**
+ * class for processing literal data objects.
+ */
+public class PGPLiteralData
+{
+ public static final char BINARY = 'b';
+ public static final char TEXT = 't';
+ public static final char UTF8 = 'u';
+
+ /**
+ * The special name indicating a "for your eyes only" packet.
+ */
+ public static final String CONSOLE = "_CONSOLE";
+
+ /**
+ * The special time for a modification time of "now" or
+ * the present time.
+ */
+ public static final Date NOW = new Date(0L);
+
+ LiteralDataPacket data;
+
+ public PGPLiteralData(
+ BCPGInputStream pIn)
+ throws IOException
+ {
+ data = (LiteralDataPacket)pIn.readPacket();
+ }
+
+ /**
+ * Return the format of the data stream - BINARY or TEXT.
+ *
+ * @return int
+ */
+ public int getFormat()
+ {
+ return data.getFormat();
+ }
+
+ /**
+ * Return the file name that's associated with the data stream.
+ *
+ * @return String
+ */
+ public String getFileName()
+ {
+ return data.getFileName();
+ }
+
+ /**
+ * Return the file name as an unintrepreted byte array.
+ */
+ public byte[] getRawFileName()
+ {
+ return data.getRawFileName();
+ }
+
+ /**
+ * Return the modification time for the file.
+ *
+ * @return the modification time.
+ */
+ public Date getModificationTime()
+ {
+ return new Date(data.getModificationTime());
+ }
+
+ /**
+ * Return the raw input stream for the data stream.
+ *
+ * @return InputStream
+ */
+ public InputStream getInputStream()
+ {
+ return data.getInputStream();
+ }
+
+ /**
+ * Return the input stream representing the data stream
+ *
+ * @return InputStream
+ */
+ public InputStream getDataStream()
+ {
+ return this.getInputStream();
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPLiteralDataGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPLiteralDataGenerator.java
new file mode 100644
index 00000000..d60b5355
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPLiteralDataGenerator.java
@@ -0,0 +1,202 @@
+package org.bouncycastle.openpgp;
+
+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.
+ */
+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);
+ }
+
+ /**
+ * Open a literal data packet for the passed in File object, returning
+ * an output stream for saving the file contents.
+ * <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 format
+ * @param file
+ * @return OutputStream
+ * @throws IOException
+ */
+ public OutputStream open(
+ OutputStream out,
+ char format,
+ File file)
+ throws IOException
+ {
+ if (pkOut != null)
+ {
+ throw new IllegalStateException("generator already in open state");
+ }
+
+ byte[] encName = Strings.toUTF8ByteArray(file.getName());
+
+ pkOut = new BCPGOutputStream(out, PacketTags.LITERAL_DATA, file.length() + 2 + encName.length + 4, oldFormat);
+
+ writeHeader(pkOut, format, encName, file.lastModified());
+
+ 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/pg/src/main/java/org/bouncycastle/openpgp/PGPMarker.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPMarker.java
new file mode 100644
index 00000000..67f3109b
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPMarker.java
@@ -0,0 +1,34 @@
+/*
+ * Created on Mar 6, 2004
+ *
+ * To change this generated comment go to
+ * Window>Preferences>Java>Code Generation>Code and Comments
+ */
+package org.bouncycastle.openpgp;
+
+import java.io.IOException;
+
+import org.bouncycastle.bcpg.BCPGInputStream;
+import org.bouncycastle.bcpg.MarkerPacket;
+
+/**
+ * a PGP marker packet - in general these should be ignored other than where
+ * the idea is to preserve the original input stream.
+ */
+public class PGPMarker
+{
+ private MarkerPacket p;
+
+ /**
+ * Default constructor.
+ *
+ * @param in
+ * @throws IOException
+ */
+ public PGPMarker(
+ BCPGInputStream in)
+ throws IOException
+ {
+ p = (MarkerPacket)in.readPacket();
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPObjectFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPObjectFactory.java
new file mode 100644
index 00000000..1d9f8df1
--- /dev/null
+++ b/pg/src/main/java/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.jcajce.JcaKeyFingerprintCalculator;
+
+/**
+ * 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 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.
+ *
+ * @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:
+ return new PGPCompressedData(in);
+ 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/pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignature.java
new file mode 100644
index 00000000..afa56362
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignature.java
@@ -0,0 +1,266 @@
+package org.bouncycastle.openpgp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+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.
+ */
+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 pubKey
+ * @param provider
+ * @throws NoSuchProviderException
+ * @throws PGPException
+ * @deprecated use init() method.
+ */
+ 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 NoSuchProviderException
+ * @throws PGPException
+ * @deprecated use init() method.
+ */
+ public void initVerify(
+ PGPPublicKey pubKey,
+ Provider provider)
+ throws PGPException
+ {
+ init(new JcaPGPContentVerifierBuilderProvider().setProvider(provider), pubKey);
+ }
+
+ /**
+ * 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 SignatureException
+ {
+ 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 SignatureException
+ {
+ 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 SignatureException
+ {
+ 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 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());
+ }
+ }
+
+ /**
+ * 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
+ {
+ 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/pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignatureList.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignatureList.java
new file mode 100644
index 00000000..e367fcd4
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignatureList.java
@@ -0,0 +1,40 @@
+package org.bouncycastle.openpgp;
+
+/**
+ * Holder for a list of PGPOnePassSignatures
+ */
+public class PGPOnePassSignatureList
+{
+ PGPOnePassSignature[] sigs;
+
+ public PGPOnePassSignatureList(
+ PGPOnePassSignature[] sigs)
+ {
+ this.sigs = new PGPOnePassSignature[sigs.length];
+
+ System.arraycopy(sigs, 0, this.sigs, 0, sigs.length);
+ }
+
+ public PGPOnePassSignatureList(
+ PGPOnePassSignature sig)
+ {
+ this.sigs = new PGPOnePassSignature[1];
+ this.sigs[0] = sig;
+ }
+
+ public PGPOnePassSignature get(
+ int index)
+ {
+ return sigs[index];
+ }
+
+ public int size()
+ {
+ return sigs.length;
+ }
+
+ public boolean isEmpty()
+ {
+ return (sigs.length == 0);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPPBEEncryptedData.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPPBEEncryptedData.java
new file mode 100644
index 00000000..c3143395
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPPBEEncryptedData.java
@@ -0,0 +1,180 @@
+package org.bouncycastle.openpgp;
+
+import java.io.EOFException;
+import java.io.InputStream;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+
+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.
+ */
+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
+ * @deprecated use PBEDataDecryptorFactory method
+ */
+ 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
+ * @deprecated use PBEDataDecryptorFactory method
+ */
+ public InputStream getDataStream(
+ char[] passPhrase,
+ Provider provider)
+ throws PGPException
+ {
+ return getDataStream(new JcePBEDataDecryptorFactoryBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider(provider).build()).setProvider(provider).build(passPhrase));
+ }
+
+ /**
+ * 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/pg/src/main/java/org/bouncycastle/openpgp/PGPPrivateKey.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPPrivateKey.java
new file mode 100644
index 00000000..52d9d1a1
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPPrivateKey.java
@@ -0,0 +1,138 @@
+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
+ * objects.
+ */
+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
+ * public key.
+ *
+ * @param privateKey private key tu use.
+ * @param keyID keyID of the corresponding public key.
+ * @deprecated use JcaPGPKeyConverter
+ */
+ public PGPPrivateKey(
+ PrivateKey privateKey,
+ long keyID)
+ {
+ 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;
+ }
+
+ /**
+ * Return the keyID associated with the contained private key.
+ *
+ * @return long
+ */
+ public long getKeyID()
+ {
+ return keyID;
+ }
+
+ /**
+ * Return the contained private key.
+ *
+ * @return PrivateKey
+ * @deprecated use a JcaPGPKeyConverter
+ */
+ public PrivateKey getKey()
+ {
+ 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/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java
new file mode 100644
index 00000000..bbe7f1c9
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java
@@ -0,0 +1,964 @@
+package org.bouncycastle.openpgp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.PublicKey;
+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.openpgp.operator.jcajce.JcaPGPKeyConverter;
+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 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.
+ * @deprecated use JcaPGPKeyConverter.getPGPPublicKey()
+ */
+ public PGPPublicKey(
+ int algorithm,
+ PublicKey pubKey,
+ Date time,
+ String provider)
+ throws PGPException, NoSuchProviderException
+ {
+ this(new JcaPGPKeyConverter().setProvider(provider).getPGPPublicKey(algorithm, pubKey, time));
+ }
+
+ /**
+ * @deprecated use JcaPGPKeyConverter.getPGPPublicKey()
+ */
+ public PGPPublicKey(
+ int algorithm,
+ PublicKey pubKey,
+ Date time)
+ throws PGPException
+ {
+ this(new JcaPGPKeyConverter().getPGPPublicKey(algorithm, pubKey, time));
+ }
+
+ /**
+ * 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 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.
+ * @deprecated use a JcaPGPKeyConverter
+ */
+ public PublicKey getKey(
+ String provider)
+ throws PGPException, NoSuchProviderException
+ {
+ 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
+ {
+ return new JcaPGPKeyConverter().setProvider(provider).getPublicKey(this);
+ }
+
+ /**
+ * 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/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java
new file mode 100644
index 00000000..27747c06
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java
@@ -0,0 +1,262 @@
+package org.bouncycastle.openpgp;
+
+import java.io.EOFException;
+import java.io.InputStream;
+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.
+ */
+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 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,
+ String provider)
+ throws PGPException, NoSuchProviderException
+ {
+ return getSymmetricAlgorithm(privKey, PGPUtil.getProvider(provider));
+ }
+
+ /**
+ *
+ * @deprecated use the method taking a PublicKeyDataDecryptorFactory
+ */
+ public int getSymmetricAlgorithm(
+ PGPPrivateKey privKey,
+ Provider provider)
+ throws PGPException, NoSuchProviderException
+ {
+ 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];
+ }
+
+ /**
+ * 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
+ * @deprecated use method that takes a PublicKeyDataDecryptorFactory
+ */
+ public InputStream getDataStream(
+ PGPPrivateKey privKey,
+ String provider)
+ throws PGPException, NoSuchProviderException
+ {
+ 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)
+ 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
+ * @deprecated use method that takes a PublicKeyDataDecryptorFactory
+ */
+ public InputStream getDataStream(
+ PGPPrivateKey privKey,
+ String asymProvider,
+ String provider)
+ throws PGPException, NoSuchProviderException
+ {
+ 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
+ {
+ 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 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/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyRing.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyRing.java
new file mode 100644
index 00000000..4480db91
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyRing.java
@@ -0,0 +1,273 @@
+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;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+
+/**
+ * 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;
+
+ /**
+ * @deprecated use version that takes a KeyFingerPrintCalculator
+ */
+ public PGPPublicKeyRing(
+ byte[] encoding)
+ throws IOException
+ {
+ this(new ByteArrayInputStream(encoding), new JcaKeyFingerprintCalculator());
+ }
+
+ public PGPPublicKeyRing(
+ byte[] encoding,
+ KeyFingerPrintCalculator fingerPrintCalculator)
+ throws IOException
+ {
+ this(new ByteArrayInputStream(encoding), fingerPrintCalculator);
+ }
+
+ /**
+ * @param pubKeys
+ */
+ PGPPublicKeyRing(
+ List pubKeys)
+ {
+ 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);
+
+ 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/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyRingCollection.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyRingCollection.java
new file mode 100644
index 00000000..f4ebe1dc
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyRingCollection.java
@@ -0,0 +1,369 @@
+package org.bouncycastle.openpgp;
+
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.util.Strings;
+
+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.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Often a PGP key ring file is made up of a succession of master/sub-key key rings.
+ * If you want to read an entire public key file in one hit this is the class for you.
+ */
+public class PGPPublicKeyRingCollection
+{
+ private Map pubRings = new HashMap();
+ private List order = new ArrayList();
+
+ private PGPPublicKeyRingCollection(
+ Map pubRings,
+ List order)
+ {
+ this.pubRings = pubRings;
+ this.order = order;
+ }
+
+ public PGPPublicKeyRingCollection(
+ byte[] encoding)
+ throws IOException, PGPException
+ {
+ this(new ByteArrayInputStream(encoding));
+ }
+
+ /**
+ * Build a PGPPublicKeyRingCollection from the passed in input stream.
+ *
+ * @param in input stream containing data
+ * @throws IOException if a problem parsing the base stream occurs
+ * @throws PGPException if an object is encountered which isn't a PGPPublicKeyRing
+ */
+ public PGPPublicKeyRingCollection(
+ InputStream in)
+ throws IOException, PGPException
+ {
+ PGPObjectFactory pgpFact = new PGPObjectFactory(in);
+ Object obj;
+
+ while ((obj = pgpFact.nextObject()) != null)
+ {
+ if (!(obj instanceof PGPPublicKeyRing))
+ {
+ throw new PGPException(obj.getClass().getName() + " found where PGPPublicKeyRing expected");
+ }
+
+ PGPPublicKeyRing pgpPub = (PGPPublicKeyRing)obj;
+ Long key = new Long(pgpPub.getPublicKey().getKeyID());
+
+ pubRings.put(key, pgpPub);
+ order.add(key);
+ }
+ }
+
+ public PGPPublicKeyRingCollection(
+ Collection collection)
+ throws IOException, PGPException
+ {
+ Iterator it = collection.iterator();
+
+ while (it.hasNext())
+ {
+ PGPPublicKeyRing pgpPub = (PGPPublicKeyRing)it.next();
+
+ Long key = new Long(pgpPub.getPublicKey().getKeyID());
+
+ pubRings.put(key, pgpPub);
+ order.add(key);
+ }
+ }
+
+ /**
+ * Return the number of rings in this collection.
+ *
+ * @return size of the collection
+ */
+ public int size()
+ {
+ return order.size();
+ }
+
+ /**
+ * return the public key rings making up this collection.
+ */
+ public Iterator getKeyRings()
+ {
+ return pubRings.values().iterator();
+ }
+
+ /**
+ * Return an iterator of the key rings associated with the passed in userID.
+ *
+ * @param userID the user ID to be matched.
+ * @return an iterator (possibly empty) of key rings which matched.
+ * @throws PGPException
+ */
+ public Iterator getKeyRings(
+ String userID)
+ throws PGPException
+ {
+ return getKeyRings(userID, false, false);
+ }
+
+ /**
+ * Return an iterator of the key rings associated with the passed in userID.
+ * <p>
+ *
+ * @param userID the user ID to be matched.
+ * @param matchPartial if true userID need only be a substring of an actual ID string to match.
+ * @return an iterator (possibly empty) of key rings which matched.
+ * @throws PGPException
+ */
+ public Iterator getKeyRings(
+ String userID,
+ boolean matchPartial)
+ throws PGPException
+ {
+ return getKeyRings(userID, matchPartial, false);
+ }
+
+ /**
+ * Return an iterator of the key rings associated with the passed in userID.
+ * <p>
+ *
+ * @param userID the user ID to be matched.
+ * @param matchPartial if true userID need only be a substring of an actual ID string to match.
+ * @param ignoreCase if true case is ignored in user ID comparisons.
+ * @return an iterator (possibly empty) of key rings which matched.
+ * @throws PGPException
+ */
+ public Iterator getKeyRings(
+ String userID,
+ boolean matchPartial,
+ boolean ignoreCase)
+ throws PGPException
+ {
+ Iterator it = this.getKeyRings();
+ List rings = new ArrayList();
+
+ if (ignoreCase)
+ {
+ userID = Strings.toLowerCase(userID);
+ }
+
+ while (it.hasNext())
+ {
+ PGPPublicKeyRing pubRing = (PGPPublicKeyRing)it.next();
+ Iterator uIt = pubRing.getPublicKey().getUserIDs();
+
+ while (uIt.hasNext())
+ {
+ String next = (String)uIt.next();
+ if (ignoreCase)
+ {
+ next = Strings.toLowerCase(next);
+ }
+
+ if (matchPartial)
+ {
+ if (next.indexOf(userID) > -1)
+ {
+ rings.add(pubRing);
+ }
+ }
+ else
+ {
+ if (next.equals(userID))
+ {
+ rings.add(pubRing);
+ }
+ }
+ }
+ }
+
+ return rings.iterator();
+ }
+
+ /**
+ * Return the PGP public key associated with the given key id.
+ *
+ * @param keyID
+ * @return the PGP public key
+ * @throws PGPException
+ */
+ public PGPPublicKey getPublicKey(
+ long keyID)
+ throws PGPException
+ {
+ Iterator it = this.getKeyRings();
+
+ while (it.hasNext())
+ {
+ PGPPublicKeyRing pubRing = (PGPPublicKeyRing)it.next();
+ PGPPublicKey pub = pubRing.getPublicKey(keyID);
+
+ if (pub != null)
+ {
+ return pub;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the public key ring which contains the key referred to by keyID.
+ *
+ * @param keyID key ID to match against
+ * @return the public key ring
+ * @throws PGPException
+ */
+ public PGPPublicKeyRing getPublicKeyRing(
+ long keyID)
+ throws PGPException
+ {
+ Long id = new Long(keyID);
+
+ if (pubRings.containsKey(id))
+ {
+ return (PGPPublicKeyRing)pubRings.get(id);
+ }
+
+ Iterator it = this.getKeyRings();
+
+ while (it.hasNext())
+ {
+ PGPPublicKeyRing pubRing = (PGPPublicKeyRing)it.next();
+ PGPPublicKey pub = pubRing.getPublicKey(keyID);
+
+ if (pub != null)
+ {
+ return pubRing;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return true if a key matching the passed in key ID is present, false otherwise.
+ *
+ * @param keyID key ID to look for.
+ * @return true if keyID present, false otherwise.
+ */
+ public boolean contains(long keyID)
+ throws PGPException
+ {
+ return getPublicKey(keyID) != null;
+ }
+
+ 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);
+ }
+
+ Iterator it = order.iterator();
+ while (it.hasNext())
+ {
+ PGPPublicKeyRing sr = (PGPPublicKeyRing)pubRings.get(it.next());
+
+ sr.encode(out);
+ }
+ }
+
+
+ /**
+ * Return a new collection object containing the contents of the passed in collection and
+ * the passed in public key ring.
+ *
+ * @param ringCollection the collection the ring to be added to.
+ * @param publicKeyRing the key ring to be added.
+ * @return a new collection merging the current one with the passed in ring.
+ * @exception IllegalArgumentException if the keyID for the passed in ring is already present.
+ */
+ public static PGPPublicKeyRingCollection addPublicKeyRing(
+ PGPPublicKeyRingCollection ringCollection,
+ PGPPublicKeyRing publicKeyRing)
+ {
+ Long key = new Long(publicKeyRing.getPublicKey().getKeyID());
+
+ if (ringCollection.pubRings.containsKey(key))
+ {
+ throw new IllegalArgumentException("Collection already contains a key with a keyID for the passed in ring.");
+ }
+
+ Map newPubRings = new HashMap(ringCollection.pubRings);
+ List newOrder = new ArrayList(ringCollection.order);
+
+ newPubRings.put(key, publicKeyRing);
+ newOrder.add(key);
+
+ return new PGPPublicKeyRingCollection(newPubRings, newOrder);
+ }
+
+ /**
+ * Return a new collection object containing the contents of this collection with
+ * the passed in public key ring removed.
+ *
+ * @param ringCollection the collection the ring to be removed from.
+ * @param publicKeyRing the key ring to be removed.
+ * @return a new collection not containing the passed in ring.
+ * @exception IllegalArgumentException if the keyID for the passed in ring not present.
+ */
+ public static PGPPublicKeyRingCollection removePublicKeyRing(
+ PGPPublicKeyRingCollection ringCollection,
+ PGPPublicKeyRing publicKeyRing)
+ {
+ Long key = new Long(publicKeyRing.getPublicKey().getKeyID());
+
+ if (!ringCollection.pubRings.containsKey(key))
+ {
+ throw new IllegalArgumentException("Collection does not contain a key with a keyID for the passed in ring.");
+ }
+
+ Map newPubRings = new HashMap(ringCollection.pubRings);
+ List newOrder = new ArrayList(ringCollection.order);
+
+ newPubRings.remove(key);
+
+ for (int i = 0; i < newOrder.size(); i++)
+ {
+ Long r = (Long)newOrder.get(i);
+
+ if (r.longValue() == key.longValue())
+ {
+ newOrder.remove(i);
+ break;
+ }
+ }
+
+ return new PGPPublicKeyRingCollection(newPubRings, newOrder);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java
new file mode 100644
index 00000000..8ac0bd86
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java
@@ -0,0 +1,937 @@
+package org.bouncycastle.openpgp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Date;
+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;
+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
+{
+ 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);
+ }
+ }
+
+ /**
+ * @deprecated use method taking PBESecretKeyEncryptor
+ */
+ 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);
+ }
+
+ /**
+ * @deprecated use method taking PBESecretKeyEncryptor
+ */
+ 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,
+ 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,
+ 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, 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(
+ 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);
+ }
+ }
+
+ /**
+ * @deprecated use method taking PBESecretKeyEncryptor
+ */
+ 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);
+ }
+
+ /**
+ * @deprecated use method taking PBESecretKeyEncryptor
+ */
+ 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);
+ }
+
+ /**
+ * @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);
+ }
+
+ /**
+ * 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 passPhrase
+ * @param provider
+ * @return PGPPrivateKey
+ * @throws PGPException
+ * @throws NoSuchProviderException
+ * @deprecated use method that takes a PBESecretKeyDecryptor
+ */
+ 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
+ * @deprecated use method that takes a PBESecretKeyDecryptor
+ */
+ public PGPPrivateKey extractPrivateKey(
+ char[] passPhrase,
+ Provider provider)
+ throws PGPException
+ {
+ 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;
+ }
+
+ 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 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
+ * @deprecated use method taking PBESecretKeyDecryptor and PBESecretKeyEncryptor
+ */
+ 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 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.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();
+ }
+ 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);
+ }
+
+ /**
+ * 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
+ * @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/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java
new file mode 100644
index 00000000..2e30e737
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java
@@ -0,0 +1,480 @@
+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.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 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.
+ * <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;
+ }
+
+ /**
+ * @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();
+
+ 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 master key and sub keys encrypted
+ * using a new password and the passed in algorithm.
+ *
+ * @param ring the PGPSecretKeyRing 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
+ * @deprecated use version taking PBESecretKeyEncryptor/PBESecretKeyDecryptor
+ */
+ public static PGPSecretKeyRing copyWithNewPassword(
+ PGPSecretKeyRing ring,
+ char[] oldPassPhrase,
+ char[] newPassPhrase,
+ int newEncAlgorithm,
+ SecureRandom rand,
+ String provider)
+ throws PGPException, NoSuchProviderException
+ {
+ return copyWithNewPassword(ring, oldPassPhrase, newPassPhrase, newEncAlgorithm, rand, PGPUtil.getProvider(provider));
+ }
+
+ /**
+ * Return a copy of the passed in secret key ring, with the master key and sub keys encrypted
+ * using a new password and the passed in algorithm.
+ *
+ * @param ring the PGPSecretKeyRing 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 provider to use
+ * @deprecated use version taking PBESecretKeyEncryptor/PBESecretKeyDecryptor
+ */
+ public static PGPSecretKeyRing copyWithNewPassword(
+ PGPSecretKeyRing ring,
+ char[] oldPassPhrase,
+ char[] newPassPhrase,
+ int newEncAlgorithm,
+ SecureRandom rand,
+ Provider provider)
+ throws PGPException
+ {
+ List newKeys = new ArrayList(ring.keys.size());
+
+ for (Iterator keys = ring.getSecretKeys(); keys.hasNext();)
+ {
+ newKeys.add(PGPSecretKey.copyWithNewPassword((PGPSecretKey)keys.next(), oldPassPhrase, newPassPhrase, newEncAlgorithm, rand, provider));
+ }
+
+ return new PGPSecretKeyRing(newKeys, ring.extraPubKeys);
+ }
+
+ /**
+ * 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/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRingCollection.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRingCollection.java
new file mode 100644
index 00000000..d8fb4802
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRingCollection.java
@@ -0,0 +1,367 @@
+package org.bouncycastle.openpgp;
+
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.util.Strings;
+
+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.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Often a PGP key ring file is made up of a succession of master/sub-key key rings.
+ * If you want to read an entire secret key file in one hit this is the class for you.
+ */
+public class PGPSecretKeyRingCollection
+{
+ private Map secretRings = new HashMap();
+ private List order = new ArrayList();
+
+ private PGPSecretKeyRingCollection(
+ Map secretRings,
+ List order)
+ {
+ this.secretRings = secretRings;
+ this.order = order;
+ }
+
+ public PGPSecretKeyRingCollection(
+ byte[] encoding)
+ throws IOException, PGPException
+ {
+ this(new ByteArrayInputStream(encoding));
+ }
+
+ /**
+ * Build a PGPSecretKeyRingCollection from the passed in input stream.
+ *
+ * @param in input stream containing data
+ * @throws IOException if a problem parsinh the base stream occurs
+ * @throws PGPException if an object is encountered which isn't a PGPSecretKeyRing
+ */
+ public PGPSecretKeyRingCollection(
+ InputStream in)
+ throws IOException, PGPException
+ {
+ PGPObjectFactory pgpFact = new PGPObjectFactory(in);
+ Object obj;
+
+ while ((obj = pgpFact.nextObject()) != null)
+ {
+ if (!(obj instanceof PGPSecretKeyRing))
+ {
+ throw new PGPException(obj.getClass().getName() + " found where PGPSecretKeyRing expected");
+ }
+
+ PGPSecretKeyRing pgpSecret = (PGPSecretKeyRing)obj;
+ Long key = new Long(pgpSecret.getPublicKey().getKeyID());
+
+ secretRings.put(key, pgpSecret);
+ order.add(key);
+ }
+ }
+
+ public PGPSecretKeyRingCollection(
+ Collection collection)
+ throws IOException, PGPException
+ {
+ Iterator it = collection.iterator();
+
+ while (it.hasNext())
+ {
+ PGPSecretKeyRing pgpSecret = (PGPSecretKeyRing)it.next();
+ Long key = new Long(pgpSecret.getPublicKey().getKeyID());
+
+ secretRings.put(key, pgpSecret);
+ order.add(key);
+ }
+ }
+
+ /**
+ * Return the number of rings in this collection.
+ *
+ * @return size of the collection
+ */
+ public int size()
+ {
+ return order.size();
+ }
+
+ /**
+ * return the secret key rings making up this collection.
+ */
+ public Iterator getKeyRings()
+ {
+ return secretRings.values().iterator();
+ }
+
+ /**
+ * Return an iterator of the key rings associated with the passed in userID.
+ *
+ * @param userID the user ID to be matched.
+ * @return an iterator (possibly empty) of key rings which matched.
+ * @throws PGPException
+ */
+ public Iterator getKeyRings(
+ String userID)
+ throws PGPException
+ {
+ return getKeyRings(userID, false, false);
+ }
+
+ /**
+ * Return an iterator of the key rings associated with the passed in userID.
+ * <p>
+ *
+ * @param userID the user ID to be matched.
+ * @param matchPartial if true userID need only be a substring of an actual ID string to match.
+ * @return an iterator (possibly empty) of key rings which matched.
+ * @throws PGPException
+ */
+ public Iterator getKeyRings(
+ String userID,
+ boolean matchPartial)
+ throws PGPException
+ {
+ return getKeyRings(userID, matchPartial, false);
+ }
+
+ /**
+ * Return an iterator of the key rings associated with the passed in userID.
+ * <p>
+ *
+ * @param userID the user ID to be matched.
+ * @param matchPartial if true userID need only be a substring of an actual ID string to match.
+ * @param ignoreCase if true case is ignored in user ID comparisons.
+ * @return an iterator (possibly empty) of key rings which matched.
+ * @throws PGPException
+ */
+ public Iterator getKeyRings(
+ String userID,
+ boolean matchPartial,
+ boolean ignoreCase)
+ throws PGPException
+ {
+ Iterator it = this.getKeyRings();
+ List rings = new ArrayList();
+
+ if (ignoreCase)
+ {
+ userID = Strings.toLowerCase(userID);
+ }
+
+ while (it.hasNext())
+ {
+ PGPSecretKeyRing secRing = (PGPSecretKeyRing)it.next();
+ Iterator uIt = secRing.getSecretKey().getUserIDs();
+
+ while (uIt.hasNext())
+ {
+ String next = (String)uIt.next();
+ if (ignoreCase)
+ {
+ next = Strings.toLowerCase(next);
+ }
+
+ if (matchPartial)
+ {
+ if (next.indexOf(userID) > -1)
+ {
+ rings.add(secRing);
+ }
+ }
+ else
+ {
+ if (next.equals(userID))
+ {
+ rings.add(secRing);
+ }
+ }
+ }
+ }
+
+ return rings.iterator();
+ }
+
+ /**
+ * Return the PGP secret key associated with the given key id.
+ *
+ * @param keyID
+ * @return the secret key
+ * @throws PGPException
+ */
+ public PGPSecretKey getSecretKey(
+ long keyID)
+ throws PGPException
+ {
+ Iterator it = this.getKeyRings();
+
+ while (it.hasNext())
+ {
+ PGPSecretKeyRing secRing = (PGPSecretKeyRing)it.next();
+ PGPSecretKey sec = secRing.getSecretKey(keyID);
+
+ if (sec != null)
+ {
+ return sec;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the secret key ring which contains the key referred to by keyID.
+ *
+ * @param keyID
+ * @return the secret key ring
+ * @throws PGPException
+ */
+ public PGPSecretKeyRing getSecretKeyRing(
+ long keyID)
+ throws PGPException
+ {
+ Long id = new Long(keyID);
+
+ if (secretRings.containsKey(id))
+ {
+ return (PGPSecretKeyRing)secretRings.get(id);
+ }
+
+ Iterator it = this.getKeyRings();
+
+ while (it.hasNext())
+ {
+ PGPSecretKeyRing secretRing = (PGPSecretKeyRing)it.next();
+ PGPSecretKey secret = secretRing.getSecretKey(keyID);
+
+ if (secret != null)
+ {
+ return secretRing;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return true if a key matching the passed in key ID is present, false otherwise.
+ *
+ * @param keyID key ID to look for.
+ * @return true if keyID present, false otherwise.
+ */
+ public boolean contains(long keyID)
+ throws PGPException
+ {
+ return getSecretKey(keyID) != null;
+ }
+
+ 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);
+ }
+
+ Iterator it = order.iterator();
+ while (it.hasNext())
+ {
+ PGPSecretKeyRing sr = (PGPSecretKeyRing)secretRings.get(it.next());
+
+ sr.encode(out);
+ }
+ }
+
+ /**
+ * Return a new collection object containing the contents of the passed in collection and
+ * the passed in secret key ring.
+ *
+ * @param ringCollection the collection the ring to be added to.
+ * @param secretKeyRing the key ring to be added.
+ * @return a new collection merging the current one with the passed in ring.
+ * @exception IllegalArgumentException if the keyID for the passed in ring is already present.
+ */
+ public static PGPSecretKeyRingCollection addSecretKeyRing(
+ PGPSecretKeyRingCollection ringCollection,
+ PGPSecretKeyRing secretKeyRing)
+ {
+ Long key = new Long(secretKeyRing.getPublicKey().getKeyID());
+
+ if (ringCollection.secretRings.containsKey(key))
+ {
+ throw new IllegalArgumentException("Collection already contains a key with a keyID for the passed in ring.");
+ }
+
+ Map newSecretRings = new HashMap(ringCollection.secretRings);
+ List newOrder = new ArrayList(ringCollection.order);
+
+ newSecretRings.put(key, secretKeyRing);
+ newOrder.add(key);
+
+ return new PGPSecretKeyRingCollection(newSecretRings, newOrder);
+ }
+
+ /**
+ * Return a new collection object containing the contents of this collection with
+ * the passed in secret key ring removed.
+ *
+ * @param ringCollection the collection the ring to be removed from.
+ * @param secretKeyRing the key ring to be removed.
+ * @return a new collection merging the current one with the passed in ring.
+ * @exception IllegalArgumentException if the keyID for the passed in ring is not present.
+ */
+ public static PGPSecretKeyRingCollection removeSecretKeyRing(
+ PGPSecretKeyRingCollection ringCollection,
+ PGPSecretKeyRing secretKeyRing)
+ {
+ Long key = new Long(secretKeyRing.getPublicKey().getKeyID());
+
+ if (!ringCollection.secretRings.containsKey(key))
+ {
+ throw new IllegalArgumentException("Collection does not contain a key with a keyID for the passed in ring.");
+ }
+
+ Map newSecretRings = new HashMap(ringCollection.secretRings);
+ List newOrder = new ArrayList(ringCollection.order);
+
+ newSecretRings.remove(key);
+
+ for (int i = 0; i < newOrder.size(); i++)
+ {
+ Long r = (Long)newOrder.get(i);
+
+ if (r.longValue() == key.longValue())
+ {
+ newOrder.remove(i);
+ break;
+ }
+ }
+
+ return new PGPSecretKeyRingCollection(newSecretRings, newOrder);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java
new file mode 100644
index 00000000..5c928084
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java
@@ -0,0 +1,564 @@
+package org.bouncycastle.openpgp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+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.
+ */
+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();
+ }
+
+ /**
+ * @deprecated use init(PGPContentVerifierBuilderProvider, PGPPublicKey)
+ */
+ public void initVerify(
+ PGPPublicKey pubKey,
+ String provider)
+ throws NoSuchProviderException, PGPException
+ {
+ initVerify(pubKey, PGPUtil.getProvider(provider));
+ }
+
+ /**
+ * @deprecated use init(PGPContentVerifierBuilderProvider, PGPPublicKey)
+ */
+ public void initVerify(
+ PGPPublicKey pubKey,
+ Provider provider)
+ throws PGPException
+ {
+ 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);
+
+ lastb = 0;
+ sigOut = verifier.getOutputStream();
+ }
+
+ public void update(
+ byte b)
+ throws SignatureException
+ {
+ 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 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
+ {
+ 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
+ {
+ try
+ {
+ sigOut.write(this.getSignatureTrailer());
+
+ sigOut.close();
+ }
+ catch (IOException e)
+ {
+ throw new SignatureException(e.getMessage());
+ }
+
+ return verifier.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
+ {
+ 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
+ * @throws SignatureException
+ */
+ public boolean verifyCertification(
+ String id,
+ 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.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 SignatureException
+ * @throws PGPException
+ */
+ public boolean verifyCertification(
+ PGPPublicKey masterKey,
+ PGPPublicKey pubKey)
+ throws SignatureException, 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 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.
+ *
+ * @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 (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/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureGenerator.java
new file mode 100644
index 00000000..4ee38bdf
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureGenerator.java
@@ -0,0 +1,574 @@
+package org.bouncycastle.openpgp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+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 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 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
+ * @deprecated use method taking a PGPContentSignerBuilder
+ */
+ public PGPSignatureGenerator(
+ int keyAlgorithm,
+ int hashAlgorithm,
+ String provider)
+ throws NoSuchAlgorithmException, NoSuchProviderException, PGPException
+ {
+ 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,
+ 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
+ * @deprecated use method taking a PGPContentSignerBuilder
+ */
+ public PGPSignatureGenerator(
+ int keyAlgorithm,
+ String sigProvider,
+ int hashAlgorithm,
+ String digProvider)
+ throws NoSuchAlgorithmException, NoSuchProviderException, PGPException
+ {
+ 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,
+ int hashAlgorithm,
+ Provider digProvider)
+ throws NoSuchAlgorithmException, PGPException
+ {
+ this.providedKeyAlgorithm = keyAlgorithm;
+ this.contentSignerBuilder = new JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm).setProvider(sigProvider).setDigestProvider(digProvider);
+ }
+
+ /**
+ * 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 SignatureException
+ {
+ 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 SignatureException
+ {
+ this.update(b, 0, b.length);
+ }
+
+ public void update(
+ byte[] b,
+ int off,
+ int len)
+ throws SignatureException
+ {
+ 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 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)
+ {
+ 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
+ * @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, 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 SignatureException
+ * @throws PGPException
+ */
+ public PGPSignature generateCertification(
+ String id,
+ PGPPublicKey pubKey)
+ throws SignatureException, 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 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/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureList.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureList.java
new file mode 100644
index 00000000..39a747c9
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureList.java
@@ -0,0 +1,40 @@
+package org.bouncycastle.openpgp;
+
+/**
+ * A list of PGP signatures - normally in the signature block after literal data.
+ */
+public class PGPSignatureList
+{
+ PGPSignature[] sigs;
+
+ public PGPSignatureList(
+ PGPSignature[] sigs)
+ {
+ this.sigs = new PGPSignature[sigs.length];
+
+ System.arraycopy(sigs, 0, this.sigs, 0, sigs.length);
+ }
+
+ public PGPSignatureList(
+ PGPSignature sig)
+ {
+ this.sigs = new PGPSignature[1];
+ this.sigs[0] = sig;
+ }
+
+ public PGPSignature get(
+ int index)
+ {
+ return sigs[index];
+ }
+
+ public int size()
+ {
+ return sigs.length;
+ }
+
+ public boolean isEmpty()
+ {
+ return (sigs.length == 0);
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java
new file mode 100644
index 00000000..8c1d243d
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java
@@ -0,0 +1,197 @@
+package org.bouncycastle.openpgp;
+
+import java.io.IOException;
+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.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;
+import org.bouncycastle.bcpg.sig.TrustSignature;
+
+/**
+ * Generator for signature subpackets.
+ */
+public class PGPSignatureSubpacketGenerator
+{
+ List list = new ArrayList();
+
+ public PGPSignatureSubpacketGenerator()
+ {
+ }
+
+ public void setRevocable(boolean isCritical, boolean isRevocable)
+ {
+ list.add(new Revocable(isCritical, isRevocable));
+ }
+
+ 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.
+ *
+ * @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)
+ {
+ 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.
+ *
+ * @param isCritical true if should be treated as critical, false otherwise.
+ * @param 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.
+ * 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)
+ {
+ 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.
+ */
+ public void setSignatureCreationTime(boolean isCritical, Date date)
+ {
+ list.add(new SignatureCreationTime(isCritical, date));
+ }
+
+ public void setPreferredHashAlgorithms(boolean isCritical, int[] algorithms)
+ {
+ list.add(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_HASH_ALGS, isCritical,
+ algorithms));
+ }
+
+ public void setPreferredSymmetricAlgorithms(boolean isCritical, int[] algorithms)
+ {
+ list.add(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_SYM_ALGS, isCritical,
+ algorithms));
+ }
+
+ public void setPreferredCompressionAlgorithms(boolean isCritical, int[] algorithms)
+ {
+ list.add(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_COMP_ALGS, isCritical,
+ algorithms));
+ }
+
+ public void setKeyFlags(boolean isCritical, int flags)
+ {
+ list.add(new KeyFlags(isCritical, flags));
+ }
+
+ 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)
+ throws IOException
+ {
+ byte[] sig = pgpSignature.getEncoded();
+ byte[] data;
+
+ if (sig.length - 1 > 256)
+ {
+ data = new byte[sig.length - 3];
+ }
+ else
+ {
+ data = new byte[sig.length - 2];
+ }
+
+ System.arraycopy(sig, sig.length - data.length, data, 0, data.length);
+
+ list.add(new EmbeddedSignature(isCritical, data));
+ }
+
+ public void setPrimaryUserID(boolean isCritical, boolean isPrimaryUserID)
+ {
+ list.add(new PrimaryUserID(isCritical, isPrimaryUserID));
+ }
+
+ 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()]));
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java
new file mode 100644
index 00000000..837cf9c3
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java
@@ -0,0 +1,277 @@
+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;
+
+/**
+ * Container for a list of signature subpackets.
+ */
+public class PGPSignatureSubpacketVector
+{
+ SignatureSubpacket[] packets;
+
+ PGPSignatureSubpacketVector(
+ SignatureSubpacket[] packets)
+ {
+ this.packets = packets;
+ }
+
+ public SignatureSubpacket getSubpacket(
+ int type)
+ {
+ for (int i = 0; i != packets.length; i++)
+ {
+ if (packets[i].getType() == type)
+ {
+ return packets[i];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return true if a particular subpacket type exists.
+ *
+ * @param type type to look for.
+ * @return true if present, false otherwise.
+ */
+ public boolean hasSubpacket(
+ int type)
+ {
+ return getSubpacket(type) != null;
+ }
+
+ /**
+ * Return all signature subpackets of the passed in type.
+ * @param type subpacket type code
+ * @return an array of zero or more matching subpackets.
+ */
+ public SignatureSubpacket[] getSubpackets(
+ int type)
+ {
+ List list = new ArrayList();
+
+ for (int i = 0; i != packets.length; i++)
+ {
+ if (packets[i].getType() == type)
+ {
+ list.add(packets[i]);
+ }
+ }
+
+ return (SignatureSubpacket[])list.toArray(new SignatureSubpacket[]{});
+ }
+
+ public NotationData[] getNotationDataOccurences()
+ {
+ SignatureSubpacket[] notations = getSubpackets(SignatureSubpacketTags.NOTATION_DATA);
+ NotationData[] vals = new NotationData[notations.length];
+ for (int i = 0; i < notations.length; i++)
+ {
+ vals[i] = (NotationData)notations[i];
+ }
+
+ return vals;
+ }
+
+ public long getIssuerKeyID()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.ISSUER_KEY_ID);
+
+ if (p == null)
+ {
+ return 0;
+ }
+
+ return ((IssuerKeyID)p).getKeyID();
+ }
+
+ public Date getSignatureCreationTime()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.CREATION_TIME);
+
+ if (p == null)
+ {
+ return null;
+ }
+
+ return ((SignatureCreationTime)p).getTime();
+ }
+
+ /**
+ * Return the number of seconds a signature is valid for after its creation date. A value of zero means
+ * the signature never expires.
+ *
+ * @return seconds a signature is valid for.
+ */
+ public long getSignatureExpirationTime()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.EXPIRE_TIME);
+
+ if (p == null)
+ {
+ return 0;
+ }
+
+ return ((SignatureExpirationTime)p).getTime();
+ }
+
+ /**
+ * Return the number of seconds a key is valid for after its creation date. A value of zero means
+ * the key never expires.
+ *
+ * @return seconds a key is valid for.
+ */
+ public long getKeyExpirationTime()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.KEY_EXPIRE_TIME);
+
+ if (p == null)
+ {
+ return 0;
+ }
+
+ return ((KeyExpirationTime)p).getTime();
+ }
+
+ public int[] getPreferredHashAlgorithms()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.PREFERRED_HASH_ALGS);
+
+ if (p == null)
+ {
+ return null;
+ }
+
+ return ((PreferredAlgorithms)p).getPreferences();
+ }
+
+ public int[] getPreferredSymmetricAlgorithms()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.PREFERRED_SYM_ALGS);
+
+ if (p == null)
+ {
+ return null;
+ }
+
+ return ((PreferredAlgorithms)p).getPreferences();
+ }
+
+ public int[] getPreferredCompressionAlgorithms()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.PREFERRED_COMP_ALGS);
+
+ if (p == null)
+ {
+ return null;
+ }
+
+ return ((PreferredAlgorithms)p).getPreferences();
+ }
+
+ public int getKeyFlags()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.KEY_FLAGS);
+
+ if (p == null)
+ {
+ return 0;
+ }
+
+ return ((KeyFlags)p).getFlags();
+ }
+
+ public String getSignerUserID()
+ {
+ SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.SIGNER_USER_ID);
+
+ if (p == null)
+ {
+ return null;
+ }
+
+ return ((SignerUserID)p).getID();
+ }
+
+ public boolean isPrimaryUserID()
+ {
+ PrimaryUserID primaryId = (PrimaryUserID)this.getSubpacket(SignatureSubpacketTags.PRIMARY_USER_ID);
+
+ if (primaryId != null)
+ {
+ return primaryId.isPrimaryUserID();
+ }
+
+ return false;
+ }
+
+ public int[] getCriticalTags()
+ {
+ int count = 0;
+
+ for (int i = 0; i != packets.length; i++)
+ {
+ if (packets[i].isCritical())
+ {
+ count++;
+ }
+ }
+
+ int[] list = new int[count];
+
+ count = 0;
+
+ for (int i = 0; i != packets.length; i++)
+ {
+ if (packets[i].isCritical())
+ {
+ list[count++] = packets[i].getType();
+ }
+ }
+
+ 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.
+ *
+ * @return size of the packet vector.
+ */
+ public int size()
+ {
+ return packets.length;
+ }
+
+ SignatureSubpacket[] toSubpacketArray()
+ {
+ return packets;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPUserAttributeSubpacketVector.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPUserAttributeSubpacketVector.java
new file mode 100644
index 00000000..bf8ffe32
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPUserAttributeSubpacketVector.java
@@ -0,0 +1,93 @@
+package org.bouncycastle.openpgp;
+
+import org.bouncycastle.bcpg.UserAttributeSubpacket;
+import org.bouncycastle.bcpg.UserAttributeSubpacketTags;
+import org.bouncycastle.bcpg.attr.ImageAttribute;
+
+/**
+ * Container for a list of user attribute subpackets.
+ */
+public class PGPUserAttributeSubpacketVector
+{
+ UserAttributeSubpacket[] packets;
+
+ PGPUserAttributeSubpacketVector(
+ UserAttributeSubpacket[] packets)
+ {
+ this.packets = packets;
+ }
+
+ public UserAttributeSubpacket getSubpacket(
+ int type)
+ {
+ for (int i = 0; i != packets.length; i++)
+ {
+ if (packets[i].getType() == type)
+ {
+ return packets[i];
+ }
+ }
+
+ return null;
+ }
+
+ public ImageAttribute getImageAttribute()
+ {
+ UserAttributeSubpacket p = this.getSubpacket(UserAttributeSubpacketTags.IMAGE_ATTRIBUTE);
+
+ if (p == null)
+ {
+ return null;
+ }
+
+ return (ImageAttribute)p;
+ }
+
+ UserAttributeSubpacket[] toSubpacketArray()
+ {
+ return packets;
+ }
+
+ public boolean equals(
+ Object o)
+ {
+ if (o == this)
+ {
+ return true;
+ }
+
+ if (o instanceof PGPUserAttributeSubpacketVector)
+ {
+ PGPUserAttributeSubpacketVector other = (PGPUserAttributeSubpacketVector)o;
+
+ if (other.packets.length != packets.length)
+ {
+ return false;
+ }
+
+ for (int i = 0; i != packets.length; i++)
+ {
+ if (!other.packets[i].equals(packets[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public int hashCode()
+ {
+ int code = 0;
+
+ for (int i = 0; i != packets.length; i++)
+ {
+ code ^= packets[i].hashCode();
+ }
+
+ return code;
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPUserAttributeSubpacketVectorGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPUserAttributeSubpacketVectorGenerator.java
new file mode 100644
index 00000000..07f54177
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPUserAttributeSubpacketVectorGenerator.java
@@ -0,0 +1,27 @@
+package org.bouncycastle.openpgp;
+
+import org.bouncycastle.bcpg.UserAttributeSubpacket;
+import org.bouncycastle.bcpg.attr.ImageAttribute;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PGPUserAttributeSubpacketVectorGenerator
+{
+ private List list = new ArrayList();
+
+ public void setImageAttribute(int imageType, byte[] imageData)
+ {
+ if (imageData == null)
+ {
+ throw new IllegalArgumentException("attempt to set null image");
+ }
+
+ list.add(new ImageAttribute(imageType, imageData));
+ }
+
+ public PGPUserAttributeSubpacketVector generate()
+ {
+ return new PGPUserAttributeSubpacketVector((UserAttributeSubpacket[])list.toArray(new UserAttributeSubpacket[list.size()]));
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPUtil.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPUtil.java
new file mode 100644
index 00000000..e1db922e
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPUtil.java
@@ -0,0 +1,376 @@
+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.NoSuchProviderException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Date;
+
+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.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;
+ }
+
+ 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;
+ }
+
+ /**
+ * 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()));
+ pipeFileContents(file, pOut, 4096);
+ }
+
+ /**
+ * 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);
+ 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);
+ }
+
+ pOut.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 BufferedInputStreamExt(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;
+ }
+
+ 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/pg/src/main/java/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java
new file mode 100644
index 00000000..b57236e5
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java
@@ -0,0 +1,286 @@
+package org.bouncycastle.openpgp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+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 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.
+ *
+ * @param keyAlgorithm
+ * @param hashAlgorithm
+ * @param provider
+ * @throws NoSuchAlgorithmException
+ * @throws NoSuchProviderException
+ * @throws PGPException
+ * @deprecated use constructor taking PGPContentSignerBuilder.
+ */
+ public PGPV3SignatureGenerator(
+ int keyAlgorithm,
+ int hashAlgorithm,
+ String provider)
+ throws NoSuchAlgorithmException, NoSuchProviderException, PGPException
+ {
+ 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,
+ Provider provider)
+ throws NoSuchAlgorithmException, PGPException
+ {
+ 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;
+ }
+
+ /**
+ * 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 SignatureException
+ {
+ 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 SignatureException
+ {
+ this.update(b, 0, b.length);
+ }
+
+ public void update(
+ byte[] b,
+ int off,
+ int len)
+ throws SignatureException
+ {
+ 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 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.
+ *
+ * @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
+ * @throws SignatureException
+ */
+ public PGPSignature generate()
+ throws PGPException, SignatureException
+ {
+ 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/pg/src/main/java/org/bouncycastle/openpgp/StreamGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/StreamGenerator.java
new file mode 100644
index 00000000..58628d13
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/StreamGenerator.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.openpgp;
+
+import java.io.IOException;
+
+interface StreamGenerator
+{
+ void close()
+ throws IOException;
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/WrappedGeneratorStream.java b/pg/src/main/java/org/bouncycastle/openpgp/WrappedGeneratorStream.java
new file mode 100644
index 00000000..5b9191d9
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/WrappedGeneratorStream.java
@@ -0,0 +1,46 @@
+package org.bouncycastle.openpgp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+class WrappedGeneratorStream
+ extends OutputStream
+{
+ private final OutputStream _out;
+ private final StreamGenerator _sGen;
+
+ public WrappedGeneratorStream(OutputStream out, StreamGenerator sGen)
+ {
+ _out = out;
+ _sGen = sGen;
+ }
+ public void write(byte[] bytes)
+ throws IOException
+ {
+ _out.write(bytes);
+ }
+
+ public void write(byte[] bytes, int offset, int length)
+ throws IOException
+ {
+ _out.write(bytes, offset, length);
+ }
+
+ public void write(int b)
+ throws IOException
+ {
+ _out.write(b);
+ }
+
+ public void flush()
+ throws IOException
+ {
+ _out.flush();
+ }
+
+ public void close()
+ throws IOException
+ {
+ _sGen.close();
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/examples/ByteArrayHandler.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/ByteArrayHandler.java
new file mode 100644
index 00000000..09edf25e
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/examples/ByteArrayHandler.java
@@ -0,0 +1,206 @@
+package org.bouncycastle.openpgp.examples;
+
+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.PGPLiteralDataGenerator;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPPBEEncryptedData;
+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.
+ * This service routine provides the basic PGP services between
+ * byte arrays.
+ *
+ * Note: this code plays no attention to -CONSOLE in the file name
+ * the specification of "_CONSOLE" in the filename.
+ * It also expects that a single pass phrase will have been used.
+ *
+ */
+public class ByteArrayHandler
+{
+ /**
+ * decrypt the passed in message stream
+ *
+ * @param encrypted The message to be decrypted.
+ * @param passPhrase Pass phrase (key)
+ *
+ * @return Clear text as a byte array. I18N considerations are
+ * not handled by this routine
+ * @exception IOException
+ * @exception PGPException
+ * @exception NoSuchProviderException
+ */
+ public static byte[] decrypt(
+ byte[] encrypted,
+ char[] passPhrase)
+ throws IOException, PGPException, NoSuchProviderException
+ {
+ InputStream in = new ByteArrayInputStream(encrypted);
+
+ in = PGPUtil.getDecoderStream(in);
+
+ PGPObjectFactory pgpF = new PGPObjectFactory(in);
+ PGPEncryptedDataList enc;
+ Object o = pgpF.nextObject();
+
+ //
+ // the first object might be a PGP marker packet.
+ //
+ if (o instanceof PGPEncryptedDataList)
+ {
+ enc = (PGPEncryptedDataList)o;
+ }
+ else
+ {
+ enc = (PGPEncryptedDataList)pgpF.nextObject();
+ }
+
+ PGPPBEEncryptedData pbe = (PGPPBEEncryptedData)enc.get(0);
+
+ InputStream clear = pbe.getDataStream(new JcePBEDataDecryptorFactoryBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(passPhrase));
+
+ PGPObjectFactory pgpFact = new PGPObjectFactory(clear);
+
+ PGPCompressedData cData = (PGPCompressedData)pgpFact.nextObject();
+
+ pgpFact = new PGPObjectFactory(cData.getDataStream());
+
+ PGPLiteralData ld = (PGPLiteralData)pgpFact.nextObject();
+
+ return Streams.readAll(ld.getInputStream());
+ }
+
+ /**
+ * Simple PGP encryptor between byte[].
+ *
+ * @param clearData The test to be encrypted
+ * @param passPhrase The pass phrase (key). This method assumes that the
+ * key is a simple pass phrase, and does not yet support
+ * RSA or more sophisiticated keying.
+ * @param fileName File name. This is used in the Literal Data Packet (tag 11)
+ * which is really inly important if the data is to be
+ * related to a file to be recovered later. Because this
+ * routine does not know the source of the information, the
+ * caller can set something here for file name use that
+ * will be carried. If this routine is being used to
+ * encrypt SOAP MIME bodies, for example, use the file name from the
+ * MIME type, if applicable. Or anything else appropriate.
+ *
+ * @param armor
+ *
+ * @return encrypted data.
+ * @exception IOException
+ * @exception PGPException
+ * @exception NoSuchProviderException
+ */
+ public static byte[] encrypt(
+ byte[] clearData,
+ char[] passPhrase,
+ String fileName,
+ int algorithm,
+ boolean armor)
+ throws IOException, PGPException, NoSuchProviderException
+ {
+ if (fileName == null)
+ {
+ fileName= PGPLiteralData.CONSOLE;
+ }
+
+ byte[] compressedData = compress(clearData, fileName, CompressionAlgorithmTags.ZIP);
+
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ OutputStream out = bOut;
+ if (armor)
+ {
+ out = new ArmoredOutputStream(out);
+ }
+
+ 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);
+
+ 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,
+ // in which case we would pass in bOut.
+ OutputStream pOut = lData.open(cos, // the compressed output stream
+ PGPLiteralData.BINARY,
+ fileName, // "filename" to store
+ clearData.length, // length of clear data
+ new Date() // current time
+ );
+
+ pOut.write(clearData);
+ pOut.close();
+
+ comData.close();
+
+ return bOut.toByteArray();
+ }
+
+ public static void main(String[] args) throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ String passPhrase = "Dick Beck";
+ char[] passArray = passPhrase.toCharArray();
+
+ byte[] original = "Hello world".getBytes();
+ System.out.println("Starting PGP test");
+ byte[] encrypted = encrypt(original, passArray, "iway", PGPEncryptedDataGenerator.CAST5, true);
+
+ System.out.println("\nencrypted data = '"+new String(encrypted)+"'");
+ byte[] decrypted= decrypt(encrypted,passArray);
+
+ System.out.println("\ndecrypted data = '"+new String(decrypted)+"'");
+
+ encrypted = encrypt(original, passArray, "iway", PGPEncryptedDataGenerator.AES_256, false);
+
+ System.out.println("\nencrypted data = '"+new String(org.bouncycastle.util.encoders.Hex.encode(encrypted))+"'");
+ decrypted= decrypt(encrypted, passArray);
+
+ System.out.println("\ndecrypted data = '"+new String(decrypted)+"'");
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/examples/ClearSignedFileProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/ClearSignedFileProcessor.java
new file mode 100644
index 00000000..eb0dbc50
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/examples/ClearSignedFileProcessor.java
@@ -0,0 +1,390 @@
+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;
+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.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 creates clear signed files and verifies them.
+ * <p>
+ * To sign a file: ClearSignedFileProcessor -s fileName secretKey passPhrase.<br>
+ * <p>
+ * To decrypt: ClearSignedFileProcessor -v fileName signatureFile publicKeyFile.
+ */
+public class ClearSignedFileProcessor
+{
+ private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
+ throws IOException
+ {
+ bOut.reset();
+
+ int lookAhead = -1;
+ int ch;
+
+ while ((ch = fIn.read()) >= 0)
+ {
+ bOut.write(ch);
+ if (ch == '\r' || ch == '\n')
+ {
+ lookAhead = readPassedEOL(bOut, ch, fIn);
+ break;
+ }
+ }
+
+ return lookAhead;
+ }
+
+ private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn)
+ throws IOException
+ {
+ bOut.reset();
+
+ int ch = lookAhead;
+
+ do
+ {
+ bOut.write(ch);
+ if (ch == '\r' || ch == '\n')
+ {
+ lookAhead = readPassedEOL(bOut, ch, fIn);
+ break;
+ }
+ }
+ while ((ch = fIn.read()) >= 0);
+
+ if (ch < 0)
+ {
+ lookAhead = -1;
+ }
+
+ return lookAhead;
+ }
+
+ private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
+ throws IOException
+ {
+ int lookAhead = fIn.read();
+
+ if (lastCh == '\r' && lookAhead == '\n')
+ {
+ bOut.write(lookAhead);
+ lookAhead = fIn.read();
+ }
+
+ return lookAhead;
+ }
+
+ /*
+ * verify a clear text signed file
+ */
+ private static void verifyFile(
+ InputStream in,
+ InputStream keyIn,
+ String resultName)
+ throws Exception
+ {
+ ArmoredInputStream aIn = new ArmoredInputStream(in);
+ OutputStream out = new BufferedOutputStream(new FileOutputStream(resultName));
+
+
+
+ //
+ // write out signed section using the local line separator.
+ // 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);
+ byte[] lineSep = getLineSeparator();
+
+ if (lookAhead != -1 && aIn.isClearText())
+ {
+ byte[] line = lineOut.toByteArray();
+ out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
+ out.write(lineSep);
+
+ while (lookAhead != -1 && aIn.isClearText())
+ {
+ lookAhead = readInputLine(lineOut, lookAhead, aIn);
+
+ line = lineOut.toByteArray();
+ out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
+ out.write(lineSep);
+ }
+ }
+
+ out.close();
+
+ PGPPublicKeyRingCollection pgpRings = new PGPPublicKeyRingCollection(keyIn);
+
+ PGPObjectFactory pgpFact = new PGPObjectFactory(aIn);
+ PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
+ PGPSignature sig = p3.get(0);
+
+ PGPPublicKey publicKey = pgpRings.getPublicKey(sig.getKeyID());
+ sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), publicKey);
+
+ //
+ // read the input, making sure we ignore the last newline.
+ //
+
+ InputStream sigIn = new BufferedInputStream(new FileInputStream(resultName));
+
+ lookAhead = readInputLine(lineOut, sigIn);
+
+ processLine(sig, lineOut.toByteArray());
+
+ if (lookAhead != -1)
+ {
+ do
+ {
+ lookAhead = readInputLine(lineOut, lookAhead, sigIn);
+
+ sig.update((byte)'\r');
+ sig.update((byte)'\n');
+
+ processLine(sig, lineOut.toByteArray());
+ }
+ while (lookAhead != -1);
+ }
+
+ sigIn.close();
+
+ if (sig.verify())
+ {
+ System.out.println("signature verified.");
+ }
+ else
+ {
+ System.out.println("signature verification failed.");
+ }
+ }
+
+ private static byte[] getLineSeparator()
+ {
+ String nl = System.getProperty("line.separator");
+ byte[] nlBytes = new byte[nl.length()];
+
+ for (int i = 0; i != nlBytes.length; i++)
+ {
+ nlBytes[i] = (byte)nl.charAt(i);
+ }
+
+ return nlBytes;
+ }
+
+ /*
+ * create a clear text signed file.
+ */
+ private static void signFile(
+ String fileName,
+ InputStream keyIn,
+ OutputStream out,
+ char[] pass,
+ String digestName)
+ throws IOException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, SignatureException
+ {
+ int digest;
+
+ if (digestName.equals("SHA256"))
+ {
+ digest = PGPUtil.SHA256;
+ }
+ else if (digestName.equals("SHA384"))
+ {
+ digest = PGPUtil.SHA384;
+ }
+ else if (digestName.equals("SHA512"))
+ {
+ digest = PGPUtil.SHA512;
+ }
+ else if (digestName.equals("MD5"))
+ {
+ digest = PGPUtil.MD5;
+ }
+ else if (digestName.equals("RIPEMD160"))
+ {
+ digest = PGPUtil.RIPEMD160;
+ }
+ else
+ {
+ digest = PGPUtil.SHA1;
+ }
+
+ 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.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, pgpPrivKey);
+
+ Iterator it = pgpSecKey.getPublicKey().getUserIDs();
+ if (it.hasNext())
+ {
+ spGen.setSignerUserID(false, (String)it.next());
+ sGen.setHashedSubpackets(spGen.generate());
+ }
+
+ InputStream fIn = new BufferedInputStream(new FileInputStream(fileName));
+ ArmoredOutputStream aOut = new ArmoredOutputStream(out);
+
+ aOut.beginClearText(digest);
+
+ //
+ // note the last \n/\r/\r\n in the file is ignored
+ //
+ ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
+ int lookAhead = readInputLine(lineOut, fIn);
+
+ processLine(aOut, sGen, lineOut.toByteArray());
+
+ if (lookAhead != -1)
+ {
+ do
+ {
+ lookAhead = readInputLine(lineOut, lookAhead, fIn);
+
+ sGen.update((byte)'\r');
+ sGen.update((byte)'\n');
+
+ processLine(aOut, sGen, lineOut.toByteArray());
+ }
+ while (lookAhead != -1);
+ }
+
+ fIn.close();
+
+ aOut.endClearText();
+
+ BCPGOutputStream bOut = new BCPGOutputStream(aOut);
+
+ sGen.generate().encode(bOut);
+
+ aOut.close();
+ }
+
+ private static void processLine(PGPSignature sig, byte[] line)
+ throws SignatureException, IOException
+ {
+ int length = getLengthWithoutWhiteSpace(line);
+ if (length > 0)
+ {
+ sig.update(line, 0, length);
+ }
+ }
+
+ 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)
+ {
+ sGen.update(line, 0, length);
+ }
+
+ aOut.write(line, 0, line.length);
+ }
+
+ private static int getLengthWithoutSeparatorOrTrailingWhitespace(byte[] line)
+ {
+ int end = line.length - 1;
+
+ while (end >= 0 && isWhiteSpace(line[end]))
+ {
+ end--;
+ }
+
+ return end + 1;
+ }
+
+ private static boolean isLineEnding(byte b)
+ {
+ return b == '\r' || b == '\n';
+ }
+
+ private static int getLengthWithoutWhiteSpace(byte[] line)
+ {
+ int end = line.length - 1;
+
+ while (end >= 0 && isWhiteSpace(line[end]))
+ {
+ end--;
+ }
+
+ return end + 1;
+ }
+
+ private static boolean isWhiteSpace(byte b)
+ {
+ return isLineEnding(b) || b == '\t' || b == ' ';
+ }
+
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (args[0].equals("-s"))
+ {
+ InputStream keyIn = PGPUtil.getDecoderStream(new FileInputStream(args[2]));
+ FileOutputStream out = new FileOutputStream(args[1] + ".asc");
+
+ if (args.length == 4)
+ {
+ signFile(args[1], keyIn, out, args[3].toCharArray(), "SHA1");
+ }
+ else
+ {
+ signFile(args[1], keyIn, out, args[3].toCharArray(), args[4]);
+ }
+ }
+ else if (args[0].equals("-v"))
+ {
+ if (args[1].indexOf(".asc") < 0)
+ {
+ System.err.println("file needs to end in \".asc\"");
+ System.exit(1);
+ }
+ FileInputStream in = new FileInputStream(args[1]);
+ InputStream keyIn = PGPUtil.getDecoderStream(new FileInputStream(args[2]));
+
+ verifyFile(in, keyIn, args[1].substring(0, args[1].length() - 4));
+ }
+ else
+ {
+ System.err.println("usage: ClearSignedFileProcessor [-s file keyfile passPhrase]|[-v sigFile keyFile]");
+ }
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java
new file mode 100644
index 00000000..bdd3295a
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java
@@ -0,0 +1,139 @@
+package org.bouncycastle.openpgp.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchProviderException;
+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;
+import org.bouncycastle.openpgp.PGPException;
+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
+ * key and an El Gamal key for encryption.
+ * <p>
+ * usage: DSAElGamalKeyRingGenerator [-a] identity passPhrase
+ * <p>
+ * Where identity is the name to be associated with the public key. The keys are placed
+ * in the files pub.[asc|bpg] and secret.[asc|bpg].
+ * <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 widely supported.
+ */
+public class DSAElGamalKeyRingGenerator
+{
+ private static void exportKeyPair(
+ OutputStream secretOut,
+ OutputStream publicOut,
+ KeyPair dsaKp,
+ KeyPair elgKp,
+ String identity,
+ char[] passPhrase,
+ boolean armor)
+ throws IOException, InvalidKeyException, NoSuchProviderException, SignatureException, PGPException
+ {
+ if (armor)
+ {
+ secretOut = new ArmoredOutputStream(secretOut);
+ }
+
+ 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, sha1Calc, null, null, new JcaPGPContentSignerBuilder(dsaKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1), new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, sha1Calc).setProvider("BC").build(passPhrase));
+
+ keyRingGen.addSubKey(elgKeyPair);
+
+ keyRingGen.generateSecretKeyRing().encode(secretOut);
+
+ secretOut.close();
+
+ if (armor)
+ {
+ publicOut = new ArmoredOutputStream(publicOut);
+ }
+
+ keyRingGen.generatePublicKeyRing().encode(publicOut);
+
+ publicOut.close();
+ }
+
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (args.length < 2)
+ {
+ System.out.println("DSAElGamalKeyRingGenerator [-a] identity passPhrase");
+ System.exit(0);
+ }
+
+ KeyPairGenerator dsaKpg = KeyPairGenerator.getInstance("DSA", "BC");
+
+ dsaKpg.initialize(1024);
+
+ //
+ // 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();
+
+ if (args[0].equals("-a"))
+ {
+ if (args.length < 3)
+ {
+ System.out.println("DSAElGamalKeyRingGenerator [-a] identity passPhrase");
+ System.exit(0);
+ }
+
+ FileOutputStream out1 = new FileOutputStream("secret.asc");
+ FileOutputStream out2 = new FileOutputStream("pub.asc");
+
+ exportKeyPair(out1, out2, dsaKp, elgKp, args[1], args[2].toCharArray(), true);
+ }
+ else
+ {
+ FileOutputStream out1 = new FileOutputStream("secret.bpg");
+ FileOutputStream out2 = new FileOutputStream("pub.bpg");
+
+ exportKeyPair(out1, out2, dsaKp, elgKp, args[0], args[1].toCharArray(), false);
+ }
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/examples/DetachedSignatureProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/DetachedSignatureProcessor.java
new file mode 100644
index 00000000..3339f956
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/examples/DetachedSignatureProcessor.java
@@ -0,0 +1,198 @@
+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.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
+ {
+ 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
+ {
+ 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
+ {
+ 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
+ {
+ 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/pg/src/main/java/org/bouncycastle/openpgp/examples/DirectKeySignature.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/DirectKeySignature.java
new file mode 100644
index 00000000..7142141a
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/examples/DirectKeySignature.java
@@ -0,0 +1,135 @@
+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;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+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.PGPSignatureSubpacketGenerator;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
+import org.bouncycastle.openpgp.PGPUtil;
+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
+ * the current working directory.
+ * <p>
+ * To sign a key: DirectKeySignature secretKeyFile secretKeyPass publicKeyFile(key to be signed) NotationName NotationValue.<br/>
+ * </p><p>
+ * To display a NotationData packet from a publicKey previously signed: DirectKeySignature signedPublicKeyFile.<br/>
+ * </p><p>
+ * <b>Note</b>: this example will silently overwrite files, nor does it pay any attention to
+ * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase
+ * will have been used.
+ * </p>
+ */
+public class DirectKeySignature
+{
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (args.length == 1)
+ {
+ 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
+ Iterator iter = key.getSignaturesOfType(PGPSignature.DIRECT_KEY);
+ while(iter.hasNext())
+ {
+ PGPSignature sig = (PGPSignature)iter.next();
+
+ System.out.println("Signature date is: " + sig.getHashedSubPackets().getSignatureCreationTime());
+
+ NotationData[] data = sig.getHashedSubPackets().getNotationDataOccurences();//.getSubpacket(SignatureSubpacketTags.NOTATION_DATA);
+
+ for (int i = 0; i < data.length; i++)
+ {
+ System.out.println("Found Notaion named '"+data[i].getNotationName()+"' with content '"+data[i].getNotationValue()+"'.");
+ }
+ }
+ }
+ else if (args.length == 5)
+ {
+ // gather command line arguments
+ 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])), new JcaKeyFingerprintCalculator());
+ String notationName = args[3];
+ String notationValue = args[4];
+
+ // create the signed keyRing
+ 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
+ ArmoredOutputStream out = new ArmoredOutputStream(new FileOutputStream("SignedKey.asc"));
+ sRing.encode(out);
+ out.flush();
+ out.close();
+ }
+ else
+ {
+ System.err.println("usage: DirectKeySignature secretKeyFile secretKeyPass publicKeyFile(key to be signed) NotationName NotationValue");
+ System.err.println("or: DirectKeySignature signedPublicKeyFile");
+
+ }
+ }
+
+ private static byte[] signPublicKey(PGPSecretKey secretKey, String secretKeyPass, PGPPublicKey keyToBeSigned, String notationName, String notationValue, boolean armor) throws Exception
+ {
+ OutputStream out = new ByteArrayOutputStream();
+
+ if (armor)
+ {
+ out = new ArmoredOutputStream(out);
+ }
+
+ PGPPrivateKey pgpPrivKey = secretKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(secretKeyPass.toCharArray()));
+
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("BC"));
+
+ sGen.init(PGPSignature.DIRECT_KEY, pgpPrivKey);
+
+ BCPGOutputStream bOut = new BCPGOutputStream(out);
+
+ sGen.generateOnePassVersion(false).encode(bOut);
+
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+
+ boolean isHumanReadable = true;
+
+ spGen.setNotationData(true, isHumanReadable, notationName, notationValue);
+
+ PGPSignatureSubpacketVector packetVector = spGen.generate();
+ sGen.setHashedSubpackets(packetVector);
+
+ bOut.flush();
+
+ if (armor)
+ {
+ out.close();
+ }
+
+ return PGPPublicKey.addCertification(keyToBeSigned, sGen.generate()).getEncoded();
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/examples/KeyBasedFileProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/KeyBasedFileProcessor.java
new file mode 100644
index 00000000..1f350bb0
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/examples/KeyBasedFileProcessor.java
@@ -0,0 +1,279 @@
+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.PGPEncryptedData;
+import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedDataList;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPOnePassSignatureList;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
+import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
+import org.bouncycastle.openpgp.PGPUtil;
+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
+ * encryption files.
+ * <p>
+ * To encrypt a file: KeyBasedFileProcessor -e [-a|-ai] fileName publicKeyFile.<br>
+ * If -a is specified the output file will be "ascii-armored".
+ * If -i is specified the output file will be have integrity checking added.
+ * <p>
+ * To decrypt: KeyBasedFileProcessor -d fileName secretKeyFile passPhrase.
+ * <p>
+ * Note 1: this example will silently overwrite files, nor does it pay any attention to
+ * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase
+ * will have been used.
+ * <p>
+ * Note 2: if an empty file name has been specified in the literal data object contained in the
+ * encrypted packet a file with the name filename.out will be generated in the current working directory.
+ */
+public class KeyBasedFileProcessor
+{
+ private static void decryptFile(
+ String inputFileName,
+ String keyFileName,
+ char[] passwd,
+ String defaultFileName)
+ throws IOException, NoSuchProviderException
+ {
+ 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
+ */
+ private static void decryptFile(
+ InputStream in,
+ InputStream keyIn,
+ char[] passwd,
+ String defaultFileName)
+ throws IOException, NoSuchProviderException
+ {
+ in = PGPUtil.getDecoderStream(in);
+
+ try
+ {
+ PGPObjectFactory pgpF = new PGPObjectFactory(in);
+ PGPEncryptedDataList enc;
+
+ Object o = pgpF.nextObject();
+ //
+ // the first object might be a PGP marker packet.
+ //
+ if (o instanceof PGPEncryptedDataList)
+ {
+ enc = (PGPEncryptedDataList)o;
+ }
+ else
+ {
+ enc = (PGPEncryptedDataList)pgpF.nextObject();
+ }
+
+ //
+ // find the secret key
+ //
+ Iterator it = enc.getEncryptedDataObjects();
+ PGPPrivateKey sKey = null;
+ PGPPublicKeyEncryptedData pbe = null;
+ PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
+ PGPUtil.getDecoderStream(keyIn));
+
+ while (sKey == null && it.hasNext())
+ {
+ pbe = (PGPPublicKeyEncryptedData)it.next();
+
+ sKey = PGPExampleUtil.findSecretKey(pgpSec, pbe.getKeyID(), passwd);
+ }
+
+ if (sKey == null)
+ {
+ throw new IllegalArgumentException("secret key for message not found.");
+ }
+
+ InputStream clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").build(sKey));
+
+ PGPObjectFactory plainFact = new PGPObjectFactory(clear);
+
+ Object message = plainFact.nextObject();
+
+ if (message instanceof PGPCompressedData)
+ {
+ PGPCompressedData cData = (PGPCompressedData)message;
+ PGPObjectFactory pgpFact = new PGPObjectFactory(cData.getDataStream());
+
+ message = pgpFact.nextObject();
+ }
+
+ if (message instanceof PGPLiteralData)
+ {
+ PGPLiteralData ld = (PGPLiteralData)message;
+
+ String outFileName = ld.getFileName();
+ if (outFileName.length() == 0)
+ {
+ outFileName = defaultFileName;
+ }
+
+ InputStream unc = ld.getInputStream();
+ OutputStream fOut = new BufferedOutputStream(new FileOutputStream(outFileName));
+
+ Streams.pipeAll(unc, fOut);
+
+ fOut.close();
+ }
+ else if (message instanceof PGPOnePassSignatureList)
+ {
+ throw new PGPException("encrypted message contains a signed message - not literal data.");
+ }
+ else
+ {
+ throw new PGPException("message is not a simple encrypted file - type unknown.");
+ }
+
+ if (pbe.isIntegrityProtected())
+ {
+ if (!pbe.verify())
+ {
+ System.err.println("message failed integrity check");
+ }
+ else
+ {
+ System.err.println("message integrity check passed");
+ }
+ }
+ else
+ {
+ System.err.println("no message integrity check");
+ }
+ }
+ catch (PGPException e)
+ {
+ System.err.println(e);
+ if (e.getUnderlyingException() != null)
+ {
+ e.getUnderlyingException().printStackTrace();
+ }
+ }
+ }
+
+ 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
+ {
+ 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();
+
+ if (armor)
+ {
+ out.close();
+ }
+ }
+ catch (PGPException e)
+ {
+ System.err.println(e);
+ if (e.getUnderlyingException() != null)
+ {
+ e.getUnderlyingException().printStackTrace();
+ }
+ }
+ }
+
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (args.length == 0)
+ {
+ 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"))
+ {
+ encryptFile(args[2] + ".asc", args[2], args[3], true, (args[1].indexOf('i') > 0));
+ }
+ else if (args[1].equals("-i"))
+ {
+ encryptFile(args[2] + ".bpg", args[2], args[3], false, true);
+ }
+ else
+ {
+ encryptFile(args[1] + ".bpg", args[1], args[2], false, false);
+ }
+ }
+ else if (args[0].equals("-d"))
+ {
+ decryptFile(args[1], args[2], args[3].toCharArray(), new File(args[1]).getName() + ".out");
+ }
+ else
+ {
+ System.err.println("usage: KeyBasedFileProcessor -d|-e [-a|ai] file [secretKeyFile passPhrase|pubKeyFile]");
+ }
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/examples/KeyBasedLargeFileProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/KeyBasedLargeFileProcessor.java
new file mode 100644
index 00000000..8ffbcc49
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/examples/KeyBasedLargeFileProcessor.java
@@ -0,0 +1,283 @@
+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;
+import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedData;
+import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedDataList;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPOnePassSignatureList;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
+import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
+import org.bouncycastle.openpgp.PGPUtil;
+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
+ * encryption large files.
+ * <p>
+ * To encrypt a file: KeyBasedLargeFileProcessor -e [-a|-ai] fileName publicKeyFile.<br>
+ * If -a is specified the output file will be "ascii-armored".
+ * If -i is specified the output file will be have integrity checking added.
+ * <p>
+ * To decrypt: KeyBasedLargeFileProcessor -d fileName secretKeyFile passPhrase.
+ * <p>
+ * Note 1: this example will silently overwrite files, nor does it pay any attention to
+ * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase
+ * will have been used.
+ * <p>
+ * Note 2: this example generates partial packets to encode the file, the output it generates
+ * will not be readable by older PGP products or products that don't support partial packet
+ * encoding.
+ * <p>
+ * Note 3: if an empty file name has been specified in the literal data object contained in the
+ * encrypted packet a file with the name filename.out will be generated in the current working directory.
+ */
+public class KeyBasedLargeFileProcessor
+{
+ private static void decryptFile(
+ String inputFileName,
+ String keyFileName,
+ char[] passwd,
+ String defaultFileName)
+ throws IOException, NoSuchProviderException
+ {
+ 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
+ */
+ private static void decryptFile(
+ InputStream in,
+ InputStream keyIn,
+ char[] passwd,
+ String defaultFileName)
+ throws IOException, NoSuchProviderException
+ {
+ in = PGPUtil.getDecoderStream(in);
+
+ try
+ {
+ PGPObjectFactory pgpF = new PGPObjectFactory(in);
+ PGPEncryptedDataList enc;
+
+ Object o = pgpF.nextObject();
+ //
+ // the first object might be a PGP marker packet.
+ //
+ if (o instanceof PGPEncryptedDataList)
+ {
+ enc = (PGPEncryptedDataList)o;
+ }
+ else
+ {
+ enc = (PGPEncryptedDataList)pgpF.nextObject();
+ }
+
+ //
+ // find the secret key
+ //
+ Iterator it = enc.getEncryptedDataObjects();
+ PGPPrivateKey sKey = null;
+ PGPPublicKeyEncryptedData pbe = null;
+ PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
+ PGPUtil.getDecoderStream(keyIn));
+
+ while (sKey == null && it.hasNext())
+ {
+ pbe = (PGPPublicKeyEncryptedData)it.next();
+
+ sKey = PGPExampleUtil.findSecretKey(pgpSec, pbe.getKeyID(), passwd);
+ }
+
+ if (sKey == null)
+ {
+ throw new IllegalArgumentException("secret key for message not found.");
+ }
+
+ InputStream clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").build(sKey));
+
+ PGPObjectFactory plainFact = new PGPObjectFactory(clear);
+
+ PGPCompressedData cData = (PGPCompressedData)plainFact.nextObject();
+
+ InputStream compressedStream = new BufferedInputStream(cData.getDataStream());
+ PGPObjectFactory pgpFact = new PGPObjectFactory(compressedStream);
+
+ Object message = pgpFact.nextObject();
+
+ if (message instanceof PGPLiteralData)
+ {
+ PGPLiteralData ld = (PGPLiteralData)message;
+
+ String outFileName = ld.getFileName();
+ if (outFileName.length() == 0)
+ {
+ outFileName = defaultFileName;
+ }
+
+ InputStream unc = ld.getInputStream();
+ OutputStream fOut = new BufferedOutputStream(new FileOutputStream(outFileName));
+
+ Streams.pipeAll(unc, fOut);
+
+ fOut.close();
+ }
+ else if (message instanceof PGPOnePassSignatureList)
+ {
+ throw new PGPException("encrypted message contains a signed message - not literal data.");
+ }
+ else
+ {
+ throw new PGPException("message is not a simple encrypted file - type unknown.");
+ }
+
+ if (pbe.isIntegrityProtected())
+ {
+ if (!pbe.verify())
+ {
+ System.err.println("message failed integrity check");
+ }
+ else
+ {
+ System.err.println("message integrity check passed");
+ }
+ }
+ else
+ {
+ System.err.println("no message integrity check");
+ }
+ }
+ catch (PGPException e)
+ {
+ System.err.println(e);
+ if (e.getUnderlyingException() != null)
+ {
+ e.getUnderlyingException().printStackTrace();
+ }
+ }
+ }
+
+ 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
+ {
+ PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setWithIntegrityPacket(withIntegrityCheck).setSecureRandom(new SecureRandom()).setProvider("BC"));
+
+ cPk.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encKey).setProvider("BC"));
+
+ OutputStream cOut = cPk.open(out, new byte[1 << 16]);
+
+ PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(
+ PGPCompressedData.ZIP);
+
+ PGPUtil.writeFileToLiteralData(comData.open(cOut), PGPLiteralData.BINARY, new File(fileName), new byte[1 << 16]);
+
+ comData.close();
+
+ cOut.close();
+
+ if (armor)
+ {
+ out.close();
+ }
+ }
+ catch (PGPException e)
+ {
+ System.err.println(e);
+ if (e.getUnderlyingException() != null)
+ {
+ e.getUnderlyingException().printStackTrace();
+ }
+ }
+ }
+
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (args.length == 0)
+ {
+ System.err.println("usage: KeyBasedLargeFileProcessor -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"))
+ {
+ encryptFile(args[2] + ".asc", args[2], args[3], true, (args[1].indexOf('i') > 0));
+ }
+ else if (args[1].equals("-i"))
+ {
+ encryptFile(args[2] + ".bpg", args[2], args[3], false, true);
+ }
+ else
+ {
+ encryptFile(args[1] + ".bpg", args[1], args[2], false, false);
+ }
+ }
+ else if (args[0].equals("-d"))
+ {
+ decryptFile(args[1], args[2], args[3].toCharArray(), new File(args[1]).getName() + ".out");
+ }
+ else
+ {
+ System.err.println("usage: KeyBasedLargeFileProcessor -d|-e [-a|ai] file [secretKeyFile passPhrase|pubKeyFile]");
+ }
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/examples/PBEFileProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/PBEFileProcessor.java
new file mode 100644
index 00000000..9b5d4b96
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/examples/PBEFileProcessor.java
@@ -0,0 +1,214 @@
+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.PGPEncryptedData;
+import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedDataList;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPPBEEncryptedData;
+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;
+
+/**
+ * A simple utility class that encrypts/decrypts password based
+ * encryption files.
+ * <p>
+ * To encrypt a file: PBEFileProcessor -e [-ai] fileName passPhrase.<br>
+ * If -a is specified the output file will be "ascii-armored".<br>
+ * If -i is specified the output file will be "integrity protected".
+ * <p>
+ * To decrypt: PBEFileProcessor -d fileName passPhrase.
+ * <p>
+ * Note: this example will silently overwrite files, nor does it pay any attention to
+ * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase
+ * will have been used.
+ */
+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 IOException, NoSuchProviderException, PGPException
+ {
+ in = PGPUtil.getDecoderStream(in);
+
+ PGPObjectFactory pgpF = new PGPObjectFactory(in);
+ PGPEncryptedDataList enc;
+ Object o = pgpF.nextObject();
+
+ //
+ // the first object might be a PGP marker packet.
+ //
+ if (o instanceof PGPEncryptedDataList)
+ {
+ enc = (PGPEncryptedDataList)o;
+ }
+ else
+ {
+ enc = (PGPEncryptedDataList)pgpF.nextObject();
+ }
+
+ PGPPBEEncryptedData pbe = (PGPPBEEncryptedData)enc.get(0);
+
+ InputStream clear = pbe.getDataStream(new JcePBEDataDecryptorFactoryBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(passPhrase));
+
+ PGPObjectFactory pgpFact = new PGPObjectFactory(clear);
+
+ //
+ // if we're trying to read a file generated by someone other than us
+ // the data might not be compressed, so we check the return type from
+ // the factory and behave accordingly.
+ //
+ o = pgpFact.nextObject();
+ if (o instanceof PGPCompressedData)
+ {
+ PGPCompressedData cData = (PGPCompressedData)o;
+
+ pgpFact = new PGPObjectFactory(cData.getDataStream());
+
+ o = pgpFact.nextObject();
+ }
+
+ 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())
+ {
+ System.err.println("message failed integrity check");
+ }
+ else
+ {
+ System.err.println("message integrity check passed");
+ }
+ }
+ else
+ {
+ System.err.println("no message integrity check");
+ }
+ }
+
+ 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
+ {
+ if (armor)
+ {
+ out = new ArmoredOutputStream(out);
+ }
+
+ try
+ {
+ byte[] compressedData = PGPExampleUtil.compressFile(fileName, CompressionAlgorithmTags.ZIP);
+
+ 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(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (args[0].equals("-e"))
+ {
+ if (args[1].equals("-a") || args[1].equals("-ai") || args[1].equals("-ia"))
+ {
+ encryptFile(args[2] + ".asc", args[2], args[3].toCharArray(), true, (args[1].indexOf('i') > 0));
+ }
+ else if (args[1].equals("-i"))
+ {
+ encryptFile(args[2] + ".bpg", args[2], args[3].toCharArray(), false, true);
+ }
+ else
+ {
+ encryptFile(args[1] + ".bpg", args[1], args[2].toCharArray(), false, false);
+ }
+ }
+ else if (args[0].equals("-d"))
+ {
+ decryptFile(args[1], args[2].toCharArray());
+ }
+ else
+ {
+ System.err.println("usage: PBEFileProcessor -e [-ai]|-d file passPhrase");
+ }
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/examples/PGPExampleUtil.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/PGPExampleUtil.java
new file mode 100644
index 00000000..084c4683
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/examples/PubringDump.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/PubringDump.java
new file mode 100644
index 00000000..eacb478f
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/examples/PubringDump.java
@@ -0,0 +1,102 @@
+package org.bouncycastle.openpgp.examples;
+
+import java.io.*;
+
+import java.security.Security;
+import java.util.Iterator;
+
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.PGPUtil;
+
+import org.bouncycastle.util.encoders.Hex;
+
+/**
+ * Basic class which just lists the contents of the public key file passed
+ * as an argument. If the file contains more than one "key ring" they are
+ * listed in the order found.
+ */
+public class PubringDump
+{
+ public static String getAlgorithm(
+ int algId)
+ {
+ switch (algId)
+ {
+ case PublicKeyAlgorithmTags.RSA_GENERAL:
+ return "RSA_GENERAL";
+ case PublicKeyAlgorithmTags.RSA_ENCRYPT:
+ return "RSA_ENCRYPT";
+ case PublicKeyAlgorithmTags.RSA_SIGN:
+ return "RSA_SIGN";
+ case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT:
+ return "ELGAMAL_ENCRYPT";
+ case PublicKeyAlgorithmTags.DSA:
+ return "DSA";
+ case PublicKeyAlgorithmTags.EC:
+ return "EC";
+ case PublicKeyAlgorithmTags.ECDSA:
+ return "ECDSA";
+ case PublicKeyAlgorithmTags.ELGAMAL_GENERAL:
+ return "ELGAMAL_GENERAL";
+ case PublicKeyAlgorithmTags.DIFFIE_HELLMAN:
+ return "DIFFIE_HELLMAN";
+ }
+
+ return "unknown";
+ }
+
+ public static void main(String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ PGPUtil.setDefaultProvider("BC");
+
+ //
+ // Read the public key rings
+ //
+ PGPPublicKeyRingCollection pubRings = new PGPPublicKeyRingCollection(
+ PGPUtil.getDecoderStream(new FileInputStream(args[0])));
+
+ Iterator rIt = pubRings.getKeyRings();
+
+ while (rIt.hasNext())
+ {
+ PGPPublicKeyRing pgpPub = (PGPPublicKeyRing)rIt.next();
+
+ try
+ {
+ pgpPub.getPublicKey();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ continue;
+ }
+
+ Iterator it = pgpPub.getPublicKeys();
+ boolean first = true;
+ while (it.hasNext())
+ {
+ PGPPublicKey pgpKey = (PGPPublicKey)it.next();
+
+ if (first)
+ {
+ System.out.println("Key ID: " + Long.toHexString(pgpKey.getKeyID()));
+ first = false;
+ }
+ else
+ {
+ System.out.println("Key ID: " + Long.toHexString(pgpKey.getKeyID()) + " (subkey)");
+ }
+ System.out.println(" Algorithm: " + getAlgorithm(pgpKey.getAlgorithm()));
+ System.out.println(" Fingerprint: " + new String(Hex.encode(pgpKey.getFingerprint())));
+ }
+ }
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/examples/RSAKeyPairGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/RSAKeyPairGenerator.java
new file mode 100644
index 00000000..673258c1
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/examples/RSAKeyPairGenerator.java
@@ -0,0 +1,114 @@
+package org.bouncycastle.openpgp.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+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.
+ * <p>
+ * usage: RSAKeyPairGenerator [-a] identity passPhrase
+ * <p>
+ * Where identity is the name to be associated with the public key. The keys are placed
+ * in the files pub.[asc|bpg] and secret.[asc|bpg].
+ */
+public class RSAKeyPairGenerator
+{
+ private static void exportKeyPair(
+ OutputStream secretOut,
+ OutputStream publicOut,
+ PublicKey publicKey,
+ PrivateKey privateKey,
+ String identity,
+ char[] passPhrase,
+ boolean armor)
+ throws IOException, InvalidKeyException, NoSuchProviderException, SignatureException, PGPException
+ {
+ if (armor)
+ {
+ secretOut = new ArmoredOutputStream(secretOut);
+ }
+
+ 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);
+
+ secretOut.close();
+
+ if (armor)
+ {
+ publicOut = new ArmoredOutputStream(publicOut);
+ }
+
+ PGPPublicKey key = secretKey.getPublicKey();
+
+ key.encode(publicOut);
+
+ publicOut.close();
+ }
+
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+
+ kpg.initialize(1024);
+
+ KeyPair kp = kpg.generateKeyPair();
+
+ if (args.length < 2)
+ {
+ System.out.println("RSAKeyPairGenerator [-a] identity passPhrase");
+ System.exit(0);
+ }
+
+ if (args[0].equals("-a"))
+ {
+ if (args.length < 3)
+ {
+ System.out.println("RSAKeyPairGenerator [-a] identity passPhrase");
+ System.exit(0);
+ }
+
+ FileOutputStream out1 = new FileOutputStream("secret.asc");
+ FileOutputStream out2 = new FileOutputStream("pub.asc");
+
+ exportKeyPair(out1, out2, kp.getPublic(), kp.getPrivate(), args[1], args[2].toCharArray(), true);
+ }
+ else
+ {
+ FileOutputStream out1 = new FileOutputStream("secret.bpg");
+ FileOutputStream out2 = new FileOutputStream("pub.bpg");
+
+ exportKeyPair(out1, out2, kp.getPublic(), kp.getPrivate(), args[0], args[1].toCharArray(), false);
+ }
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/examples/SignedFileProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/SignedFileProcessor.java
new file mode 100644
index 00000000..28ffaee7
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/examples/SignedFileProcessor.java
@@ -0,0 +1,215 @@
+package org.bouncycastle.openpgp.examples;
+
+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.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+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.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
+import org.bouncycastle.openpgp.PGPException;
+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.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.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.
+ * <p>
+ * To sign a file: SignedFileProcessor -s [-a] fileName secretKey passPhrase.<br>
+ * If -a is specified the output file will be "ascii-armored".
+ * <p>
+ * To decrypt: SignedFileProcessor -v fileName publicKeyFile.
+ * <p>
+ * <b>Note</b>: this example will silently overwrite files, nor does it pay any attention to
+ * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase
+ * will have been used.
+ * <p>
+ * <b>Note</b>: the example also makes use of PGP compression. If you are having difficulty getting it
+ * to interoperate with other PGP programs try removing the use of compression first.
+ */
+public class SignedFileProcessor
+{
+ /*
+ * verify the passed in file as being correctly signed.
+ */
+ private static void verifyFile(
+ InputStream in,
+ InputStream keyIn)
+ throws Exception
+ {
+ in = PGPUtil.getDecoderStream(in);
+
+ PGPObjectFactory pgpFact = new PGPObjectFactory(in);
+
+ 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;
+ PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
+
+ PGPPublicKey key = pgpRing.getPublicKey(ops.getKeyID());
+ FileOutputStream out = new FileOutputStream(p2.getFileName());
+
+ ops.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), key);
+
+ while ((ch = dIn.read()) >= 0)
+ {
+ ops.update((byte)ch);
+ out.write(ch);
+ }
+
+ out.close();
+
+ PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
+
+ if (ops.verify(p3.get(0)))
+ {
+ System.out.println("signature verified.");
+ }
+ else
+ {
+ System.out.println("signature verification failed.");
+ }
+ }
+
+ /**
+ * Generate an encapsulated signed file.
+ *
+ * @param fileName
+ * @param keyIn
+ * @param out
+ * @param pass
+ * @param armor
+ * @throws IOException
+ * @throws NoSuchAlgorithmException
+ * @throws NoSuchProviderException
+ * @throws PGPException
+ * @throws SignatureException
+ */
+ private static void signFile(
+ String fileName,
+ InputStream keyIn,
+ OutputStream out,
+ 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"));
+
+ sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
+
+ Iterator it = pgpSec.getPublicKey().getUserIDs();
+ if (it.hasNext())
+ {
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+
+ spGen.setSignerUserID(false, (String)it.next());
+ sGen.setHashedSubpackets(spGen.generate());
+ }
+
+ PGPCompressedDataGenerator cGen = new PGPCompressedDataGenerator(
+ PGPCompressedData.ZLIB);
+
+ BCPGOutputStream bOut = new BCPGOutputStream(cGen.open(out));
+
+ sGen.generateOnePassVersion(false).encode(bOut);
+
+ File file = new File(fileName);
+ PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
+ OutputStream lOut = lGen.open(bOut, PGPLiteralData.BINARY, file);
+ FileInputStream fIn = new FileInputStream(file);
+ int ch;
+
+ while ((ch = fIn.read()) >= 0)
+ {
+ lOut.write(ch);
+ sGen.update((byte)ch);
+ }
+
+ lGen.close();
+
+ sGen.generate().encode(bOut);
+
+ cGen.close();
+
+ 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"))
+ {
+ FileInputStream keyIn = new FileInputStream(args[3]);
+ FileOutputStream out = new FileOutputStream(args[2] + ".asc");
+
+ signFile(args[2], keyIn, out, args[4].toCharArray(), true);
+ }
+ else
+ {
+ FileInputStream keyIn = new FileInputStream(args[2]);
+ FileOutputStream out = new FileOutputStream(args[1] + ".bpg");
+
+ signFile(args[1], keyIn, out, args[3].toCharArray(), false);
+ }
+ }
+ else if (args[0].equals("-v"))
+ {
+ FileInputStream in = new FileInputStream(args[1]);
+ FileInputStream keyIn = new FileInputStream(args[2]);
+
+ verifyFile(in, keyIn);
+ }
+ else
+ {
+ System.err.println("usage: SignedFileProcessor -v|-s [-a] file keyfile [passPhrase]");
+ }
+ }
+} \ No newline at end of file
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/KeyFingerPrintCalculator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/KeyFingerPrintCalculator.java
new file mode 100644
index 00000000..1d990a63
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PBEDataDecryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBEDataDecryptorFactory.java
new file mode 100644
index 00000000..05c93e16
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java
new file mode 100644
index 00000000..189467df
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyDecryptor.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyDecryptor.java
new file mode 100644
index 00000000..290fa1ec
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptor.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptor.java
new file mode 100644
index 00000000..0530638c
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSigner.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSigner.java
new file mode 100644
index 00000000..0427e813
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSignerBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSignerBuilder.java
new file mode 100644
index 00000000..77ec2e53
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentVerifier.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentVerifier.java
new file mode 100644
index 00000000..abee23af
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentVerifierBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentVerifierBuilder.java
new file mode 100644
index 00000000..b0dc6f84
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java
new file mode 100644
index 00000000..42717e04
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataDecryptor.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataDecryptor.java
new file mode 100644
index 00000000..7f79640a
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataDecryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataDecryptorFactory.java
new file mode 100644
index 00000000..87d7891d
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataDecryptorProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataDecryptorProvider.java
new file mode 100644
index 00000000..bfa2afdb
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataDecryptorProvider.java
@@ -0,0 +1,5 @@
+package org.bouncycastle.openpgp.operator;
+
+public interface PGPDataDecryptorProvider
+{
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataEncryptor.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataEncryptor.java
new file mode 100644
index 00000000..20d40a39
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataEncryptorBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataEncryptorBuilder.java
new file mode 100644
index 00000000..13f94775
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDigestCalculator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDigestCalculator.java
new file mode 100644
index 00000000..a917a55b
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDigestCalculatorProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDigestCalculatorProvider.java
new file mode 100644
index 00000000..bbde1ab5
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java
new file mode 100644
index 00000000..0cffaa53
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPUtil.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPUtil.java
new file mode 100644
index 00000000..61653860
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java
new file mode 100644
index 00000000..a650d770
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java
new file mode 100644
index 00000000..d6c70574
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcImplProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcImplProvider.java
new file mode 100644
index 00000000..ce8c56a0
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java
new file mode 100644
index 00000000..bb201cac
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java
new file mode 100644
index 00000000..81449ebc
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java
new file mode 100644
index 00000000..0a965fe6
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java
new file mode 100644
index 00000000..decf032f
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java
new file mode 100644
index 00000000..2258484e
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java
new file mode 100644
index 00000000..384727ef
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java
new file mode 100644
index 00000000..e13b8132
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java
new file mode 100644
index 00000000..51fd69d0
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java
new file mode 100644
index 00000000..2fea1487
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyConverter.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyConverter.java
new file mode 100644
index 00000000..2531477f
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPair.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPair.java
new file mode 100644
index 00000000..ebf0fa0b
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java
new file mode 100644
index 00000000..8f6600e0
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java
new file mode 100644
index 00000000..53e71ad2
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcUtil.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcUtil.java
new file mode 100644
index 00000000..ba55f34b
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java
new file mode 100644
index 00000000..979de84f
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/SignerOutputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/SignerOutputStream.java
new file mode 100644
index 00000000..f2bb4c97
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java
new file mode 100644
index 00000000..1bcb92c4
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java
new file mode 100644
index 00000000..e732e6bf
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java
new file mode 100644
index 00000000..3954e41b
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java
new file mode 100644
index 00000000..431f0bdc
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java
new file mode 100644
index 00000000..f42146fc
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java
new file mode 100644
index 00000000..d4db11e2
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java
new file mode 100644
index 00000000..76161db1
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java
new file mode 100644
index 00000000..08b1ba94
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java
new file mode 100644
index 00000000..29af1f01
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java
new file mode 100644
index 00000000..0cbd0cdc
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java
new file mode 100644
index 00000000..0890ddb9
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java
new file mode 100644
index 00000000..2020b6c4
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java
new file mode 100644
index 00000000..a696caf7
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java
new file mode 100644
index 00000000..97418533
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/OperatorHelper.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/OperatorHelper.java
new file mode 100644
index 00000000..c8ecde54
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/PGPUtil.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/PGPUtil.java
new file mode 100644
index 00000000..a9fefb7b
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java
new file mode 100644
index 00000000..c4f901e8
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/SignatureOutputStream.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/SignatureOutputStream.java
new file mode 100644
index 00000000..750c51fe
--- /dev/null
+++ b/pg/src/main/java/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/pg/src/test/java/org/bouncycastle/openpgp/examples/test/AllTests.java b/pg/src/test/java/org/bouncycastle/openpgp/examples/test/AllTests.java
new file mode 100644
index 00000000..f44d3724
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/examples/test/AllTests.java
@@ -0,0 +1,415 @@
+package org.bouncycastle.openpgp.examples.test;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.openpgp.examples.ClearSignedFileProcessor;
+import org.bouncycastle.openpgp.examples.DSAElGamalKeyRingGenerator;
+import org.bouncycastle.openpgp.examples.KeyBasedFileProcessor;
+import org.bouncycastle.openpgp.examples.KeyBasedLargeFileProcessor;
+import org.bouncycastle.openpgp.examples.PBEFileProcessor;
+import org.bouncycastle.openpgp.examples.RSAKeyPairGenerator;
+import org.bouncycastle.openpgp.examples.SignedFileProcessor;
+import org.bouncycastle.util.encoders.Base64;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+
+public class AllTests
+ extends TestCase
+{
+ byte[] clearSignedPublicKey = Base64.decode(
+ "mQELBEQh2+wBCAD26kte0hO6flr7Y2aetpPYutHY4qsmDPy+GwmmqVeCDkX+"
+ + "r1g7DuFbMhVeu0NkKDnVl7GsJ9VarYsFYyqu0NzLa9XS2qlTIkmJV+2/xKa1"
+ + "tzjn18fT/cnAWL88ZLCOWUr241aPVhLuIc6vpHnySpEMkCh4rvMaimnTrKwO"
+ + "42kgeDGd5cXfs4J4ovRcTbc4hmU2BRVsRjiYMZWWx0kkyL2zDVyaJSs4yVX7"
+ + "Jm4/LSR1uC/wDT0IJJuZT/gQPCMJNMEsVCziRgYkAxQK3OWojPSuv4rXpyd4"
+ + "Gvo6IbvyTgIskfpSkCnQtORNLIudQSuK7pW+LkL62N+ohuKdMvdxauOnAAYp"
+ + "tBNnZ2dnZ2dnZyA8Z2dnQGdnZ2c+iQE2BBMBAgAgBQJEIdvsAhsDBgsJCAcD"
+ + "AgQVAggDBBYCAwECHgECF4AACgkQ4M/Ier3f9xagdAf/fbKWBjLQM8xR7JkR"
+ + "P4ri8YKOQPhK+VrddGUD59/wzVnvaGyl9MZE7TXFUeniQq5iXKnm22EQbYch"
+ + "v2Jcxyt2H9yptpzyh4tP6tEHl1C887p2J4qe7F2ATua9CzVGwXQSUbKtj2fg"
+ + "UZP5SsNp25guhPiZdtkf2sHMeiotmykFErzqGMrvOAUThrO63GiYsRk4hF6r"
+ + "cQ01d+EUVpY/sBcCxgNyOiB7a84sDtrxnX5BTEZDTEj8LvuEyEV3TMUuAjx1"
+ + "7Eyd+9JtKzwV4v3hlTaWOvGro9nPS7YaPuG+RtufzXCUJPbPfTjTvtGOqvEz"
+ + "oztls8tuWA0OGHba9XfX9rfgorACAAM=");
+
+ String crOnlyMessage =
+ "\r"
+ + " hello world!\r"
+ + "\r"
+ + "- dash\r";
+
+ String nlOnlyMessage =
+ "\n"
+ + " hello world!\n"
+ + "\n"
+ + "- dash\n";
+
+ String crNlMessage =
+ "\r\n"
+ + " hello world!\r\n"
+ + "\r\n"
+ + "- dash\r\n";
+
+ String crNlMessageTrailingWhiteSpace =
+ "\r\n"
+ + " hello world! \t\r\n"
+ + "\r\n"
+ + "\r\n";
+
+ String crOnlySignedMessage =
+ "-----BEGIN PGP SIGNED MESSAGE-----\r"
+ + "Hash: SHA256\r"
+ + "\r"
+ + "\r"
+ + " hello world!\r"
+ + "\r"
+ + "- - dash\r"
+ + "-----BEGIN PGP SIGNATURE-----\r"
+ + "Version: GnuPG v1.4.2.1 (GNU/Linux)\r"
+ + "\r"
+ + "iQEVAwUBRCNS8+DPyHq93/cWAQi6SwgAj3ItmSLr/sd/ixAQLW7/12jzEjfNmFDt\r"
+ + "WOZpJFmXj0fnMzTrOILVnbxHv2Ru+U8Y1K6nhzFSR7d28n31/XGgFtdohDEaFJpx\r"
+ + "Fl+KvASKIonnpEDjFJsPIvT1/G/eCPalwO9IuxaIthmKj0z44SO1VQtmNKxdLAfK\r"
+ + "+xTnXGawXS1WUE4CQGPM45mIGSqXcYrLtJkAg3jtRa8YRUn2d7b2BtmWH+jVaVuC\r"
+ + "hNrXYv7iHFOu25yRWhUQJisvdC13D/gKIPRvARXPgPhAC2kovIy6VS8tDoyG6Hm5\r"
+ + "dMgLEGhmqsgaetVq1ZIuBZj5S4j2apBJCDpF6GBfpBOfwIZs0Tpmlw==\r"
+ + "=84Nd\r"
+ + "-----END PGP SIGNATURE-----\r";
+
+
+ String nlOnlySignedMessage =
+ "-----BEGIN PGP SIGNED MESSAGE-----\n"
+ + "Hash: SHA256\n"
+ + "\n"
+ + "\n"
+ + " hello world!\n"
+ + "\n"
+ + "- - dash\n"
+ + "-----BEGIN PGP SIGNATURE-----\n"
+ + "Version: GnuPG v1.4.2.1 (GNU/Linux)\n"
+ + "\n"
+ + "iQEVAwUBRCNS8+DPyHq93/cWAQi6SwgAj3ItmSLr/sd/ixAQLW7/12jzEjfNmFDt\n"
+ + "WOZpJFmXj0fnMzTrOILVnbxHv2Ru+U8Y1K6nhzFSR7d28n31/XGgFtdohDEaFJpx\n"
+ + "Fl+KvASKIonnpEDjFJsPIvT1/G/eCPalwO9IuxaIthmKj0z44SO1VQtmNKxdLAfK\n"
+ + "+xTnXGawXS1WUE4CQGPM45mIGSqXcYrLtJkAg3jtRa8YRUn2d7b2BtmWH+jVaVuC\n"
+ + "hNrXYv7iHFOu25yRWhUQJisvdC13D/gKIPRvARXPgPhAC2kovIy6VS8tDoyG6Hm5\n"
+ + "dMgLEGhmqsgaetVq1ZIuBZj5S4j2apBJCDpF6GBfpBOfwIZs0Tpmlw==\n"
+ + "=84Nd\n"
+ + "-----END PGP SIGNATURE-----\n";
+
+ String crNlSignedMessage =
+ "-----BEGIN PGP SIGNED MESSAGE-----\r\n"
+ + "Hash: SHA256\r\n"
+ + "\r\n"
+ + "\r\n"
+ + " hello world!\r\n"
+ + "\r\n"
+ + "- - dash\r\n"
+ + "-----BEGIN PGP SIGNATURE-----\r\n"
+ + "Version: GnuPG v1.4.2.1 (GNU/Linux)\r\n"
+ + "\r\n"
+ + "iQEVAwUBRCNS8+DPyHq93/cWAQi6SwgAj3ItmSLr/sd/ixAQLW7/12jzEjfNmFDt\r\n"
+ + "WOZpJFmXj0fnMzTrOILVnbxHv2Ru+U8Y1K6nhzFSR7d28n31/XGgFtdohDEaFJpx\r\n"
+ + "Fl+KvASKIonnpEDjFJsPIvT1/G/eCPalwO9IuxaIthmKj0z44SO1VQtmNKxdLAfK\r\n"
+ + "+xTnXGawXS1WUE4CQGPM45mIGSqXcYrLtJkAg3jtRa8YRUn2d7b2BtmWH+jVaVuC\r\n"
+ + "hNrXYv7iHFOu25yRWhUQJisvdC13D/gKIPRvARXPgPhAC2kovIy6VS8tDoyG6Hm5\r\n"
+ + "dMgLEGhmqsgaetVq1ZIuBZj5S4j2apBJCDpF6GBfpBOfwIZs0Tpmlw==\r\n"
+ + "=84Nd\r"
+ + "-----END PGP SIGNATURE-----\r\n";
+
+ String crNlSignedMessageTrailingWhiteSpace =
+ "-----BEGIN PGP SIGNED MESSAGE-----\r\n"
+ + "Hash: SHA256\r\n"
+ + "\r\n"
+ + "\r\n"
+ + " hello world! \t\r\n"
+ + "\r\n"
+ + "- - dash\r\n"
+ + "-----BEGIN PGP SIGNATURE-----\r\n"
+ + "Version: GnuPG v1.4.2.1 (GNU/Linux)\r\n"
+ + "\r\n"
+ + "iQEVAwUBRCNS8+DPyHq93/cWAQi6SwgAj3ItmSLr/sd/ixAQLW7/12jzEjfNmFDt\r\n"
+ + "WOZpJFmXj0fnMzTrOILVnbxHv2Ru+U8Y1K6nhzFSR7d28n31/XGgFtdohDEaFJpx\r\n"
+ + "Fl+KvASKIonnpEDjFJsPIvT1/G/eCPalwO9IuxaIthmKj0z44SO1VQtmNKxdLAfK\r\n"
+ + "+xTnXGawXS1WUE4CQGPM45mIGSqXcYrLtJkAg3jtRa8YRUn2d7b2BtmWH+jVaVuC\r\n"
+ + "hNrXYv7iHFOu25yRWhUQJisvdC13D/gKIPRvARXPgPhAC2kovIy6VS8tDoyG6Hm5\r\n"
+ + "dMgLEGhmqsgaetVq1ZIuBZj5S4j2apBJCDpF6GBfpBOfwIZs0Tpmlw==\r\n"
+ + "=84Nd\r"
+ + "-----END PGP SIGNATURE-----\r\n";
+
+ private PrintStream _oldOut;
+ private PrintStream _oldErr;
+
+ private ByteArrayOutputStream _currentOut;
+ private ByteArrayOutputStream _currentErr;
+
+ public void setUp()
+ throws Exception
+ {
+ _oldOut = System.out;
+ _oldErr = System.err;
+ _currentOut = new ByteArrayOutputStream();
+ _currentErr = new ByteArrayOutputStream();
+
+ System.setOut(new PrintStream(_currentOut));
+ System.setErr(new PrintStream(_currentErr));
+ }
+
+ public void tearDown()
+ {
+ System.setOut(_oldOut);
+ System.setErr(_oldErr);
+ }
+
+ public void testRSAKeyGeneration()
+ throws Exception
+ {
+ RSAKeyPairGenerator.main(new String[] { "test", "password" });
+
+ createSmallTestInput();
+ createLargeTestInput();
+
+ checkSigning("bpg");
+ checkKeyBasedEncryption("bpg");
+ checkLargeKeyBasedEncryption("bpg");
+
+ RSAKeyPairGenerator.main(new String[] { "-a", "test", "password" });
+
+ checkSigning("asc");
+ checkKeyBasedEncryption("asc");
+ checkLargeKeyBasedEncryption("asc");
+ }
+
+ public void testDSAElGamaleKeyGeneration()
+ throws Exception
+ {
+ DSAElGamalKeyRingGenerator.main(new String[] { "test", "password" });
+
+ createSmallTestInput();
+ createLargeTestInput();
+
+ checkSigning("bpg");
+ checkKeyBasedEncryption("bpg");
+ checkLargeKeyBasedEncryption("bpg");
+
+ DSAElGamalKeyRingGenerator.main(new String[] { "-a", "test", "password" });
+
+ checkSigning("asc");
+ checkKeyBasedEncryption("asc");
+ checkLargeKeyBasedEncryption("asc");
+ }
+
+ public void testPBEEncryption()
+ throws Exception
+ {
+ _currentErr.reset();
+
+ PBEFileProcessor.main(new String[] { "-e", "test.txt", "password" });
+
+ PBEFileProcessor.main(new String[] { "-d", "test.txt.bpg", "password" });
+
+ assertEquals("no message integrity check", getLine(_currentErr));
+
+ PBEFileProcessor.main(new String[] { "-e", "-i", "test.txt", "password" });
+
+ PBEFileProcessor.main(new String[] { "-d", "test.txt.bpg", "password" });
+
+ assertEquals("message integrity check passed", getLine(_currentErr));
+
+ PBEFileProcessor.main(new String[] { "-e", "-ai", "test.txt", "password" });
+
+ PBEFileProcessor.main(new String[] { "-d", "test.txt.asc", "password" });
+
+ assertEquals("message integrity check passed", getLine(_currentErr));
+ }
+
+ public void testClearSigned()
+ throws Exception
+ {
+ createTestFile(clearSignedPublicKey, "pub.bpg");
+
+ checkClearSignedVerify(nlOnlySignedMessage);
+ checkClearSignedVerify(crOnlySignedMessage);
+ checkClearSignedVerify(crNlSignedMessage);
+ checkClearSignedVerify(crNlSignedMessageTrailingWhiteSpace);
+
+ ClearSignedFileProcessor.main(new String[] { "-v", "test.txt.asc", "pub.bpg" });
+
+ RSAKeyPairGenerator.main(new String[] { "test", "password" });
+
+ checkClearSigned(crOnlyMessage);
+ checkClearSigned(nlOnlyMessage);
+ checkClearSigned(crNlMessage);
+ checkClearSigned(crNlMessageTrailingWhiteSpace);
+ }
+
+ public void testClearSignedBogusInput()
+ throws Exception
+ {
+ createTestFile(clearSignedPublicKey, "test.txt");
+
+ ClearSignedFileProcessor.main(new String[] { "-s", "test.txt", "secret.bpg", "password" });
+ }
+
+ private void checkClearSignedVerify(String message)
+ throws Exception
+ {
+ createTestData(message, "test.txt.asc");
+
+ ClearSignedFileProcessor.main(new String[] { "-v", "test.txt.asc", "pub.bpg" });
+ }
+
+ private void checkClearSigned(String message)
+ throws Exception
+ {
+ createTestData(message, "test.txt");
+
+ ClearSignedFileProcessor.main(new String[] { "-s", "test.txt", "secret.bpg", "password" });
+ ClearSignedFileProcessor.main(new String[] { "-v", "test.txt.asc", "pub.bpg" });
+ }
+
+ private void checkSigning(String type)
+ throws Exception
+ {
+ _currentOut.reset();
+
+ SignedFileProcessor.main(new String[] { "-s", "test.txt", "secret." + type, "password" });
+
+ SignedFileProcessor.main(new String[] { "-v", "test.txt.bpg", "pub." + type });
+
+ assertEquals("signature verified.", getLine(_currentOut));
+
+ SignedFileProcessor.main(new String[] { "-s", "-a", "test.txt", "secret." + type, "password" });
+
+ SignedFileProcessor.main(new String[] { "-v", "test.txt.asc", "pub." + type });
+
+ assertEquals("signature verified.", getLine(_currentOut));
+ }
+
+ private void checkKeyBasedEncryption(String type)
+ throws Exception
+ {
+ _currentErr.reset();
+
+ KeyBasedFileProcessor.main(new String[] { "-e", "test.txt", "pub." + type });
+
+ KeyBasedFileProcessor.main(new String[] { "-d", "test.txt.bpg", "secret." + type, "password" });
+
+ assertEquals("no message integrity check", getLine(_currentErr));
+
+ KeyBasedFileProcessor.main(new String[] { "-e", "-i", "test.txt", "pub." + type });
+
+ KeyBasedFileProcessor.main(new String[] { "-d", "test.txt.bpg", "secret." + type, "password" });
+
+ assertEquals("message integrity check passed", getLine(_currentErr));
+
+ KeyBasedFileProcessor.main(new String[] { "-e", "-ai", "test.txt", "pub." + type });
+
+ KeyBasedFileProcessor.main(new String[] { "-d", "test.txt.asc", "secret." + type, "password" });
+
+ assertEquals("message integrity check passed", getLine(_currentErr));
+ }
+
+ private void checkLargeKeyBasedEncryption(String type)
+ throws Exception
+ {
+ _currentErr.reset();
+
+ KeyBasedLargeFileProcessor.main(new String[] { "-e", "large.txt", "pub." + type });
+
+ KeyBasedLargeFileProcessor.main(new String[] { "-d", "large.txt.bpg", "secret." + type, "password" });
+
+ assertEquals("no message integrity check", getLine(_currentErr));
+
+ KeyBasedLargeFileProcessor.main(new String[] { "-e", "-i", "large.txt", "pub." + type });
+
+ KeyBasedLargeFileProcessor.main(new String[] { "-d", "large.txt.bpg", "secret." + type, "password" });
+
+ assertEquals("message integrity check passed", getLine(_currentErr));
+
+ KeyBasedLargeFileProcessor.main(new String[] { "-e", "-ai", "large.txt", "pub." + type });
+
+ KeyBasedLargeFileProcessor.main(new String[] { "-d", "large.txt.asc", "secret." + type, "password" });
+
+ assertEquals("message integrity check passed", getLine(_currentErr));
+ }
+
+ private void createSmallTestInput()
+ throws IOException
+ {
+ BufferedWriter bfOut = new BufferedWriter(new FileWriter("test.txt"));
+
+ bfOut.write("hello world!");
+ bfOut.newLine();
+
+ bfOut.close();
+ }
+
+ private void createLargeTestInput()
+ throws IOException
+ {
+ BufferedWriter bfOut = new BufferedWriter(new FileWriter("large.txt"));
+
+ for (int i = 0; i != 2000; i++)
+ {
+ bfOut.write("hello world!");
+ bfOut.newLine();
+ }
+
+ bfOut.close();
+ }
+
+ private void createTestData(String testData, String name)
+ throws IOException
+ {
+ BufferedWriter bfOut = new BufferedWriter(new FileWriter(name));
+
+ bfOut.write(testData);
+
+ bfOut.close();
+ }
+
+ private void createTestFile(byte[] keyData, String name)
+ throws IOException
+ {
+ FileOutputStream fOut = new FileOutputStream(name);
+
+ fOut.write(keyData);
+
+ fOut.close();
+ }
+
+ private String getLine(
+ ByteArrayOutputStream out)
+ throws IOException
+ {
+ BufferedReader bRd = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(out.toByteArray())));
+
+ out.reset();
+
+ return bRd.readLine();
+ }
+
+ public static void main (String[] args)
+ {
+ junit.textui.TestRunner.run(suite());
+ }
+
+ public static Test suite()
+ {
+ TestSuite suite = new TestSuite("OpenPGP Example Tests");
+
+ suite.addTestSuite(AllTests.class);
+
+ return suite;
+ }
+}
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/AllTests.java b/pg/src/test/java/org/bouncycastle/openpgp/test/AllTests.java
new file mode 100644
index 00000000..f1c54923
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/AllTests.java
@@ -0,0 +1,46 @@
+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);
+ suite.addTestSuite(PGPUnicodeTest.class);
+
+ return suite;
+ }
+}
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPDSAElGamalTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPDSAElGamalTest.java
new file mode 100644
index 00000000..545f4b30
--- /dev/null
+++ b/pg/src/test/java/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/pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPDSATest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPDSATest.java
new file mode 100644
index 00000000..fca957ac
--- /dev/null
+++ b/pg/src/test/java/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"
+ + "9EAhgCDkHkEcgilryTwd4zn0m4WzvpTJZSMBuY5MfbueletKyugZWDKwyCOc/j3o"
+ + "AduyWLDeWB5Ynj8jSUUUAdFXn/xU15dO0RbGGYC5uWwUB6L1Jx+n413F1cJa2stz"
+ + "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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/pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPKeyRingTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPKeyRingTest.java
new file mode 100644
index 00000000..edcb0849
--- /dev/null
+++ b/pg/src/test/java/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(
+ "lQOgBEBrBE4BCACjXVcNIFDQSofaIyZnALb2CRg+WY9uUqgHEEAOlPe03Cs5STM5"
+ + "HDlNmrh4TdFceJ46rxk1mQOjULES1YfHay8lCIzrD7FX4oj0r4DC14Fs1vXaSar2"
+ + "1szIpttOw3obL4A1e0p6N4jjsoG7N/pA0fEL0lSw92SoBrMbAheXRg4qNTZvdjOR"
+ + "grcuOuwgJRvPLtRXlhyLBoyhkd5mmrIDGv8QHJ/UjpeIcRXY9kn9oGXnEYcRbMaU"
+ + "VwXB4pLzWqz3ZejFI3lOxRWjm760puPOnGYlzSVBxlt2LgzUgSj1Mn+lIpWmAzsa"
+ + "xEiU4xUwEomQns72yYRZ6D3euNCibcte4SeXABEBAAEB8wqP7JkKN6oMNi1xJNqU"
+ + "vvt0OV4CCnrIFiOPCjebjH/NC4T/9pJ6BYSjYdo3VEPNhPhRS9U3071Kqbdt35J5"
+ + "kmzMq1yNStC1jkxHRCNTMsb1yIEY1v+fv8/Cy+tBpvAYiJKaox8jW3ppi9vTHZjW"
+ + "tYYq0kwAVojMovz1O3wW/pEF69UPBmPYsze+AHA1UucYYqdWO8U2tsdFJET/hYpe"
+ + "o7ppHJJCdqWzeiE1vDUrih9pP3MPpzcRS/gU7HRDb5HbfP7ghSLzByEa+2mvg5eK"
+ + "eLwNAx2OUtrVg9rJswXX7DOLa1nKPhdGrSV/qwuK4rBdaqJ/OvszVJ0Vln0T/aus"
+ + "it1PAuVROLUPqTVVN8/zkMenFbf5vtryC3GQYXvvZq+l3a4EXwrR/1pqrTfnfOuD"
+ + "GwlFhRJAqPfthxZS68/xC8qAmTtkl7j4nscNM9kSoZ3BFwSyD9B/vYHPWGlqnpGF"
+ + "k/hBXuIgl07KIeNIyEC3f1eRyaiMFqEz5yXbbTfEKirSVpHM/mpeKxG8w96aK3Je"
+ + "AV0X6ZkC4oLTp6HCG2TITUIeNxCh2rX3fhr9HvBDXBbMHgYlIcLwzNkwDX74cz/7"
+ + "nIclcubaWjEkDHP20XFicuChFc9zx6kBYuYy170snltTBgTWSuRH15W4NQqrLo37"
+ + "zyzZQubX7CObgQJu4ahquiOg4SWl6uEI7+36U0SED7sZzw8ns1LxrwOWbXuHie1i"
+ + "xCvsJ4RpJJ03iEdNdUIb77qf6AriqE92tXzcVXToBv5S2K5LdFYNJ1rWdwaKJRkt"
+ + "kmjCL67KM9WT/IagsUyU+57ao3COtqw9VWZi6ev+ubM6fIV0ZK46NEggOLph1hi2"
+ + "gZ9ew9uVuruYg7lG2Ku82N0fjrQpcGFsYXNoIGthc29kaGFuIDxwa2Fzb2RoYW5A"
+ + "dGlhYS1jcmVmLm9yZz6dA6AEQGsETwEIAOVwNCTaDZvW4dowPbET1bI5UeYY8rAG"
+ + "LYsWSUfgaFv2srMiApyBVltfi6OLcPjcUCHDBjCv4pwx/C4qcHWb8av4xQIpqQXO"
+ + "pO9NxYE1eZnel/QB7DtH12ZOnrDNmHtaXlulcKNGe1i1utlFhgzfFx6rWkRL0ENm"
+ + "kTkaQmPY4gTGymJTUhBbsSRq2ivWqQA1TPwBuda73UgslIAHRd/SUaxjXoLpMbGO"
+ + "TeqzcKGjr5XMPTs7/YgBpWPPUxMlEQIiU3ia1bxpEhx05k97ceK6TSH2oCPQA7gu"
+ + "mjxOSjKT+jEm+8jACVzymEmcXRy4D5Ztqkw/Z16pvNcu1DI5m6xHwr8AEQEAAQF7"
+ + "osMrvQieBAJFYY+x9jKPVclm+pVaMaIcHKwCTv6yUZMqbHNRTfwdCVKTdAzdlh5d"
+ + "zJNXXRu8eNwOcfnG3WrWAy59cYE389hA0pQPOh7iL2V1nITf1qdLru1HJqqLC+dy"
+ + "E5GtkNcgvQYbv7ACjQacscvnyBioYC6TATtPnHipMO0S1sXEnmUugNlW88pDln4y"
+ + "VxCtQXMBjuqMt0bURqmb+RoYhHhoCibo6sexxSnbEAPHBaW1b1Rm7l4UBSW6S5U0"
+ + "MXURE60IHfP1TBe1l/xOIxOi8qdBQCyaFW2up00EhRBy/WOO6KAYXQrRRpOs9TBq"
+ + "ic2wquwZePmErTbIttnnBcAKmpodrM/JBkn/we5fVg+FDTP8sM/Ubv0ZuM70aWmF"
+ + "v0/ZKbkCkh2YORLWl5+HR/RKShdkmmFgZZ5uzbOGxxEGKhw+Q3+QFUF7PmYOnOtv"
+ + "s9PZE3dV7ovRDoXIjfniD1+8sLUWwW5d+3NHAQnCHJrLnPx4sTHx6C0yWMcyZk6V"
+ + "fNHpLK4xDTbgoTmxJa/4l+wa0iD69h9K/Nxw/6+X/GEM5w3d/vjlK1Da6urN9myc"
+ + "GMsfiIll5DNIWdLLxCBPFmhJy653CICQLY5xkycWB7JOZUBTOEVrYr0AbBZSTkuB"
+ + "fq5p9MfH4N51M5TWnwlJnqEiGnpaK+VDeP8GniwCidTYyiocNPvghvWIzG8QGWMY"
+ + "PFncRpjFxmcY4XScYYpyRme4qyPbJhbZcgGpfeLvFKBPmNxVKJ2nXTdx6O6EbHDj"
+ + "XctWqNd1EQas7rUN728u7bk8G7m37MGqQuKCpNvOScH4TnPROBY8get0G3bC4mWz"
+ + "6emPeENnuyElfWQiHEtCZr1InjnNbb/C97O+vWu9PfsE");
+
+ 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(
+ "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"
+ + "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(
+ "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"
+ + "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(
+ "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"
+ + "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@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@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@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@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/pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPPBETest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPPBETest.java
new file mode 100644
index 00000000..80ca57a0
--- /dev/null
+++ b/pg/src/test/java/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/pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPRSATest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPRSATest.java
new file mode 100644
index 00000000..713c5770
--- /dev/null
+++ b/pg/src/test/java/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(
+ "mI0ER0JXuwEEAKNqsXwLU6gu6P2Q/HJqEJVt3A7Kp1yucn8HWVeJF9JLAKVjVU8jrvz9Bw4NwaRJ"
+ + "NGYEAgdRq8Hx3WP9FXFCIVfCdi+oQrphcHWzzBFul8sykUGT+LmcBdqQGU9WaWSJyCOmUht4j7t0"
+ + "zk/IXX0YxGmkqR+no5rTj9LMDG8AQQrFABEBAAG0P0VyaWMgSCBFY2hpZG5hIChpbWFnZSB0ZXN0"
+ + "IGtleSkgPGVyaWMuZWNoaWRuYUBib3VuY3ljYXN0bGUub3JnPoi2BBMBAgAgBQJHQle7AhsDBgsJ"
+ + "CAcDAgQVAggDBBYCAwECHgECF4AACgkQ1+RWqFFpjMTKtgP+Okqkn0gVpQyNYXM/hWX6f3UQcyXk"
+ + "2Sd/fWW0XG+LBjhhBo+lXRWK0uYF8OMdZwsSl9HimpgYD5/kNs0Seh417DioP1diOgxkgezyQgMa"
+ + "+ODZfNnIvVaBr1pHLPLeqIBxBVMWBfa4wDXnLLGu8018uvI2yBhz5vByB1ntxwgKMXCwAgAD0cf3"
+ + "x/UBEAABAQAAAAAAAAAAAAAAAP/Y/+AAEEpGSUYAAQEBAEgASAAA/+EAFkV4aWYAAE1NACoAAAAI"
+ + "AAAAAAAA/9sAQwAFAwQEBAMFBAQEBQUFBgcMCAcHBwcPCwsJDBEPEhIRDxERExYcFxMUGhURERgh"
+ + "GBodHR8fHxMXIiQiHiQcHh8e/8AACwgAOgBQAQEiAP/EABwAAAIDAAMBAAAAAAAAAAAAAAUHAAQG"
+ + "AQIIA//EADEQAAIBAwQBAwMDBAEFAAAAAAECAwQFEQAGEiExByJBExRRI2FxFTJCkQglM0OBof/a"
+ + "AAgBAQAAPwD19U3SgppWjqKqKFl8824j/Z1mku9ZV7gNraq4KKQTsachsliwBBHeBgH9/nzoyz16"
+ + "FWjnDc2AIZfB7JAA8AD89+3986KUryPTI8qcHK+5fwdV7hcEpqE1aKJYwfcQ2AB+dVaK9fdRfXSh"
+ + "m+j8t8qOIbsHB8H/AGD+2SyMroGUhgRkEHo651NTSp9RKlYt7GSTgQsMcSKfcc5LH2+T0fjHYGvp"
+ + "Y6iCo3tUzQQTU6GGIFOOO+x8EAZ/c/P51upqsS0dDc6SZDC8fJSVJDhgCMeCP9H+NZSfd1JJK0cV"
+ + "SrzSlY2X6UnukcqoQqe8Akg/PRBA0VqaRJIKinWKIRnKGQIwJUjDHGfjxx/96HbBulS1tqmqKlfp"
+ + "08hWaRzhUC9EnPQAxk5x1rWWqRKaGpiZv04GJH4CeR/8xrionlSSBpKgoJnwiRRcuuLN2fjoefHx"
+ + "8jXxmr4qSCdga2XClwPpsxHfWDgnByP4A1etks7wRioUB2jVs4/I7z++dKDf8TN6pLISzxLLToY+"
+ + "yFcr5GPn+zXe4X/b23PUG1W+53WnS5XanNNBTAsJZmZwq4C9r/3AMkjwT8Eg/v8Avtx21sd6y22q"
+ + "lvNQlRzlp3rTGY0ZvYe1bwpXI6Awez8pj073pvmuq6MbwtdLHd0nJpbnHRMlLUQA8XYnHvcGNz0B"
+ + "kYI+Dpx1N7uUVvqKeopqOKvigNVChkP28657dG4lsZIBTiSvWcgglV+n909UYfUWsG5bXUDb9fAh"
+ + "ntsMQQ0Mrxq6MCCXdeRxJx7z/jgY16Bs0tLTU6UIqRUyMOPF5+UkgVVDMOXucDIGf4/OslTb72fU"
+ + "3g0dXPV0ldG5eON3ZmGV4gkgkY78ZI0dS9W+OolqUraSESzlpAqsp4j/ACY8TyJ4gY6/nrRXbtdQ"
+ + "1bt9tVmYhR0Y2XAP4LDvrHjWH31aaup9RKOSjQEyGJm+Qce3LD+M/wCs/Gl9TekO1txbllq0tU0c"
+ + "4vrV0rrVBEkIQholQLyVUYH3HB5HonTOrUpk26bPURyB+CqKgvI/1QvEdPgkknGS3ZJz2RoDX2KV"
+ + "FFM33sMcXFqaKVm4S+9eSoASEDAkHOAPOPnQ/et1+8sENDSFVutKqs5MyH6LMwDBlDclBzkZA6x8"
+ + "aN7EmjobV9oZ6d7jURfW+4arTg8TN0XI93TAjx1yxnzpC7j3XafUf1Yjmutiqaait1XLb7M9FMpS"
+ + "rVn4F8t/i/FiGRQRkYbONO60023LhQ0VprbDRiGdTDBFPRj6kaKyj3CRM4YnIfAJ89+dFpNs7Njr"
+ + "E+w+0WSaQoTBJNDKMqSWVlcDPWc41sNpWymoqBY0Mv1PbIS8ruxLIuSSxJ7IJ0JWkqoamslkkWeS"
+ + "WokkjT6AQoqueIz7icgNlvkHwPha0l6uG3bpWR0aVkc6QKzytEkjVBMYdeKgeHOV/Yqc+NWbndI7"
+ + "laLlaKBzeq62SlWqHpP1YWIKhOL+3GT03zx8E+7WV9HtjVVFeaytpLvU3mOmhWSehriwCyszZ4sx"
+ + "OArKhyMgcSQpOAGBTbdsElBMZqT9ZaoKtc5H11LSIJIjhR0ArAjP5HzrWVOxrDWVNRRXNZa6gkjU"
+ + "pRTkPEiZPJAuP7ScdazMG1rdFQlttVVvpK1aBUwkazwtG3/jRfKHKjHE/wCIOhFRef6fVOsz1y1R"
+ + "RqaCpUqTIhJJ4kgnKlWPnkCD1jVC3/1DcSTUtHYZJbvEivS3KOqVHBK9CTrtR0O8/keTp3WiOZXl"
+ + "ldFUOeJAOSpViMfxqndrer3KnhSFzSTLO9UqAjLELhuY7DfAAI8k56xpbeplZarMLUtEhu9TU16U"
+ + "ogkBaaIHJJZemOML8g/zk6FXe97S3nXWuDa94kStpqiYVtStKsUkhiX9TkzgMMFQAy5APXz1dlu+"
+ + "5rFXz0wpTUVMiQpWtEqkuXUeSW9pJbipUHOQWx51rtsU0tBuKpo70qukcKzxyVDB5JGA4cyR1yIZ"
+ + "gQB58aWi+o94pdw+oNVS7ks9ZNYbf/0ukmUOCqEyOjMjDm/YUfOR3nTP9MKp90bAtG4YKv6UFfSf"
+ + "VhCxBWjVhgEYPtOMZHfecY0E3nX+nO1PsrJd5rTBSXuSWBpJJgFSoCmQmRs5VWwcknpgPzoL6WVE"
+ + "Mb2qorYKKkrIl+lyoeUcFQgT9PCgkyKFPFScg4yAPAbNnuQqaw00fFV4vKUYMsmTIfKkDAx8/OdG"
+ + "TpVb99EbFuW5XC+Ut2utuvtXKk4qxUNKiSIAEAjY4CDHhSp/cEAjAN6Nr6a3Gv3LadvSb+v92qGe"
+ + "CEUcdPHQuQ/J1kL+xSWBwcnrr86OUlp9WdrbWobrRWam3BuiSuWS7tUzRM81KuQEjclcOqcVDHOT"
+ + "nOc50eisl335XVFVuKwXGwKqLBFKtaq1M1JMMvGxQfpuGSNiAWwCQGznWrtPp5smyWKntNu2rbzR"
+ + "00ZWKJoVc/k5Z/JJ8knJ+dI//kjsnd1TtqA7Zj3DT2KiYA7dt8SrTiIk8uoGDv2eWDnABxrCf8d/"
+ + "TYG87g3PtGzbev8AMkppbW1xr35Wk/Ek9O8QYyEd9EY7C/nXsfbsE/8ASqOouVLTxXR6eP7v6YBA"
+ + "k4jkA2ASAcgfsNEVRFZmVFDN/cQOz/Ou2pqampqamqtPQUMFfUVsFFTRVNQFE0yRKHkC5xyYDJxk"
+ + "4z+dWtTX/9mItgQTAQIAIAUCR0JYkAIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJENfkVqhR"
+ + "aYzEAPYD/iHdLOAE8r8HHF3F4z28vtIT8iiRB9aPC/YH0xqV1qeEKG8+VosBaQAOCEquONtRWsww"
+ + "gO3XB0d6VAq2kMOKc2YiB4ZtZcFvvmP9KdmVIZxVjpa9ozjP5j9zFso1HOpFcsn/VDBEqy5TvsNx"
+ + "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@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/pg/src/test/java/org/bouncycastle/openpgp/test/DSA2Test.java b/pg/src/test/java/org/bouncycastle/openpgp/test/DSA2Test.java
new file mode 100644
index 00000000..fd7fd2df
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/DSA2Test.java
@@ -0,0 +1,290 @@
+package org.bouncycastle.openpgp.test;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.openpgp.PGPCompressedData;
+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.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.util.test.UncloseableOutputStream;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Security;
+import java.util.Date;
+
+/**
+ * GPG compatability test vectors
+ */
+public class DSA2Test
+ 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 testK1024H160()
+ throws Exception
+ {
+ doSigVerifyTest("DSA-1024-160.pub", "dsa-1024-160-sign.gpg");
+ }
+
+ public void testK1024H224()
+ throws Exception
+ {
+ doSigVerifyTest("DSA-1024-160.pub", "dsa-1024-224-sign.gpg");
+ }
+
+ public void testK1024H256()
+ throws Exception
+ {
+ doSigVerifyTest("DSA-1024-160.pub", "dsa-1024-256-sign.gpg");
+ }
+
+ public void testK1024H384()
+ throws Exception
+ {
+ doSigVerifyTest("DSA-1024-160.pub", "dsa-1024-384-sign.gpg");
+ }
+
+ public void testK1024H512()
+ throws Exception
+ {
+ doSigVerifyTest("DSA-1024-160.pub", "dsa-1024-512-sign.gpg");
+ }
+
+ public void testK2048H224()
+ throws Exception
+ {
+ doSigVerifyTest("DSA-2048-224.pub", "dsa-2048-224-sign.gpg");
+ }
+
+ public void testK3072H256()
+ throws Exception
+ {
+ doSigVerifyTest("DSA-3072-256.pub", "dsa-3072-256-sign.gpg");
+ }
+
+ public void testK7680H384()
+ throws Exception
+ {
+ doSigVerifyTest("DSA-7680-384.pub", "dsa-7680-384-sign.gpg");
+ }
+
+ public void testK15360H512()
+ throws Exception
+ {
+ doSigVerifyTest("DSA-15360-512.pub", "dsa-15360-512-sign.gpg");
+ }
+
+ public void testGenerateK1024H224()
+ throws Exception
+ {
+ doSigGenerateTest("DSA-1024-160.sec", "DSA-1024-160.pub", PGPUtil.SHA224);
+ }
+
+ public void testGenerateK1024H256()
+ throws Exception
+ {
+ doSigGenerateTest("DSA-1024-160.sec", "DSA-1024-160.pub", PGPUtil.SHA256);
+ }
+
+ public void testGenerateK1024H384()
+ throws Exception
+ {
+ doSigGenerateTest("DSA-1024-160.sec", "DSA-1024-160.pub", PGPUtil.SHA384);
+ }
+
+ public void testGenerateK1024H512()
+ throws Exception
+ {
+ doSigGenerateTest("DSA-1024-160.sec", "DSA-1024-160.pub", PGPUtil.SHA512);
+ }
+
+ public void testGenerateK2048H256()
+ throws Exception
+ {
+ doSigGenerateTest("DSA-2048-224.sec", "DSA-2048-224.pub", PGPUtil.SHA256);
+ }
+
+ public void testGenerateK2048H512()
+ throws Exception
+ {
+ doSigGenerateTest("DSA-2048-224.sec", "DSA-2048-224.pub", PGPUtil.SHA512);
+ }
+
+ private void doSigGenerateTest(String privateKeyFile, String publicKeyFile, int digest)
+ throws Exception
+ {
+ PGPSecretKeyRing secRing = loadSecretKey(privateKeyFile);
+ PGPPublicKeyRing pubRing = loadPublicKey(publicKeyFile);
+ String data = "hello world!";
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ ByteArrayInputStream testIn = new ByteArrayInputStream(data.getBytes());
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(PublicKeyAlgorithmTags.DSA, digest, "BC");
+
+ sGen.initSign(PGPSignature.BINARY_DOCUMENT, secRing.getSecretKey().extractPrivateKey("test".toCharArray(), "BC"));
+
+ BCPGOutputStream bcOut = new BCPGOutputStream(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);
+
+ PGPObjectFactory pgpFact = new PGPObjectFactory(bOut.toByteArray());
+ PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+ PGPOnePassSignature ops = p1.get(0);
+
+ assertEquals(digest, ops.getHashAlgorithm());
+ assertEquals(PublicKeyAlgorithmTags.DSA, ops.getKeyAlgorithm());
+
+ PGPLiteralData p2 = (PGPLiteralData)pgpFact.nextObject();
+ if (!p2.getModificationTime().equals(testDate))
+ {
+ fail("Modification time not preserved");
+ }
+
+ InputStream dIn = p2.getInputStream();
+
+ ops.initVerify(pubRing.getPublicKey(), "BC");
+
+ while ((ch = dIn.read()) >= 0)
+ {
+ ops.update((byte)ch);
+ }
+
+ PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
+ PGPSignature sig = p3.get(0);
+
+ assertEquals(digest, sig.getHashAlgorithm());
+ assertEquals(PublicKeyAlgorithmTags.DSA, sig.getKeyAlgorithm());
+
+ assertTrue(ops.verify(sig));
+ }
+
+ private void doSigVerifyTest(
+ String publicKeyFile,
+ String sigFile)
+ throws Exception
+ {
+ PGPPublicKeyRing publicKey = loadPublicKey(publicKeyFile);
+ PGPObjectFactory pgpFact = loadSig(sigFile);
+
+ 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();
+
+ ops.initVerify(publicKey.getPublicKey(), "BC");
+
+ int ch;
+ while ((ch = dIn.read()) >= 0)
+ {
+ ops.update((byte)ch);
+ }
+
+ PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
+
+ assertTrue(ops.verify(p3.get(0)));
+ }
+
+ private PGPObjectFactory loadSig(
+ String sigName)
+ throws Exception
+ {
+ FileInputStream fIn = new FileInputStream(getDataHome() + "/sigs/" + sigName);
+
+ return new PGPObjectFactory(fIn);
+ }
+
+ private PGPPublicKeyRing loadPublicKey(
+ String keyName)
+ throws Exception
+ {
+ FileInputStream fIn = new FileInputStream(getDataHome() + "/keys/" + keyName);
+
+ return new PGPPublicKeyRing(fIn);
+ }
+
+ private PGPSecretKeyRing loadSecretKey(
+ String keyName)
+ throws Exception
+ {
+ FileInputStream fIn = new FileInputStream(getDataHome() + "/keys/" + keyName);
+
+ return new PGPSecretKeyRing(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/dsa";
+ }
+
+ public static void main (String[] args)
+ throws Exception
+ {
+ junit.textui.TestRunner.run(suite());
+ }
+
+ public static Test suite()
+ throws Exception
+ {
+ TestSuite suite = new TestSuite("GPG DSA2 tests");
+
+ suite.addTestSuite(DSA2Test.class);
+
+ return suite;
+ }
+}
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPArmoredTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPArmoredTest.java
new file mode 100644
index 00000000..e84d4065
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPArmoredTest.java
@@ -0,0 +1,255 @@
+package org.bouncycastle.openpgp.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.ArmoredInputStream;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+
+public class PGPArmoredTest
+ extends SimpleTest
+{
+ byte[] sample = 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[] marker = Hex.decode("2d2d2d2d2d454e4420504750205055424c4943204b455920424c4f434b2d2d2d2d2d");
+
+ // Contains "Hello World!" as an armored message
+ // The 'blank line' after the headers contains (legal) whitespace - see RFC2440 6.2
+ private static final String blankLineData =
+ "-----BEGIN PGP MESSAGE-----\n"
+ + "Version: BCPG v1.32\n"
+ + "Comment: A dummy message\n"
+ + " \t \t\n"
+ + "SGVsbG8gV29ybGQh\n"
+ + "=d9Xi\n"
+ + "-----END PGP MESSAGE-----\n";
+
+ private int markerCount(
+ byte[] data)
+ {
+ int ind = 0;
+ int matches = 0;
+
+ while (ind < data.length)
+ {
+ if (data[ind] == 0x2d)
+ {
+ int count = 0;
+ while (count < marker.length)
+ {
+ if (data[ind + count] != marker[count])
+ {
+ break;
+ }
+ count++;
+ }
+
+ if (count == marker.length)
+ {
+ matches++;
+ }
+
+ ind += count;
+ }
+ else
+ {
+ ind++;
+ }
+ }
+
+ return matches;
+ }
+
+ private void blankLineTest() throws Exception
+ {
+ byte[] blankLineBytes = Strings.toByteArray(blankLineData);
+ ByteArrayInputStream bIn = new ByteArrayInputStream(blankLineBytes);
+ ArmoredInputStream aIn = new ArmoredInputStream(bIn, true);
+
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ int c;
+ while ((c = aIn.read()) >= 0)
+ {
+ bOut.write(c);
+ }
+
+ byte[] expected = Strings.toByteArray("Hello World!");
+
+ if (!Arrays.areEqual(expected, bOut.toByteArray()))
+ {
+ fail("Incorrect message retrieved in blank line test.");
+ }
+ }
+
+ public void performTest()
+ throws Exception
+ {
+ //
+ // test immediate close
+ //
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ ArmoredOutputStream aOut = new ArmoredOutputStream(bOut);
+
+ aOut.close();
+
+ byte[] data = bOut.toByteArray();
+
+ if (data.length != 0)
+ {
+ fail("No data should have been written");
+ }
+
+ //
+ // multiple close
+ //
+ bOut = new ByteArrayOutputStream();
+ aOut = new ArmoredOutputStream(bOut);
+
+ aOut.write(sample);
+
+ aOut.close();
+
+ aOut.close();
+
+ int mc = markerCount(bOut.toByteArray());
+
+ if (mc < 1)
+ {
+ fail("No end marker found");
+ }
+
+ if (mc > 1)
+ {
+ fail("More than one end marker found");
+ }
+
+ //
+ // writing and reading single objects
+ //
+ bOut = new ByteArrayOutputStream();
+ aOut = new ArmoredOutputStream(bOut);
+
+ aOut.write(sample);
+
+ aOut.close();
+
+ ArmoredInputStream aIn = new ArmoredInputStream(new ByteArrayInputStream(bOut.toByteArray()));
+
+ PGPObjectFactory fact = new PGPObjectFactory(aIn);
+ int count = 0;
+
+ while (fact.nextObject() != null)
+ {
+ count++;
+ }
+
+ if (count != 1)
+ {
+ fail("wrong number of objects found: " + count);
+ }
+
+ //
+ // writing and reading multiple objects - in single block
+ //
+ bOut = new ByteArrayOutputStream();
+ aOut = new ArmoredOutputStream(bOut);
+
+ aOut.write(sample);
+ aOut.write(sample);
+
+ aOut.close();
+
+ aIn = new ArmoredInputStream(new ByteArrayInputStream(bOut.toByteArray()));
+
+ fact = new PGPObjectFactory(aIn);
+ count = 0;
+
+ while (fact.nextObject() != null)
+ {
+ count++;
+ }
+
+ if (count != 2)
+ {
+ fail("wrong number of objects found: " + count);
+ }
+
+ //
+ // writing and reading multiple objects - in single block
+ //
+ bOut = new ByteArrayOutputStream();
+ aOut = new ArmoredOutputStream(bOut);
+
+ aOut.write(sample);
+
+ aOut.close(); // does not close underlying stream
+
+ aOut = new ArmoredOutputStream(bOut);
+
+ aOut.write(sample);
+
+ aOut.close();
+
+ aIn = new ArmoredInputStream(new ByteArrayInputStream(bOut.toByteArray()));
+
+ count = 0;
+ boolean atLeastOne;
+ do
+ {
+ atLeastOne = false;
+ fact = new PGPObjectFactory(aIn);
+
+ while (fact.nextObject() != null)
+ {
+ atLeastOne = true;
+ count++;
+ }
+ }
+ while (atLeastOne);
+
+ if (count != 2)
+ {
+ fail("wrong number of objects found: " + count);
+ }
+
+ blankLineTest();
+ }
+
+ public String getName()
+ {
+ return "PGPArmoredTest";
+ }
+
+ public static void main(
+ String[] args)
+ {
+ runTest(new PGPArmoredTest());
+ }
+}
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPClearSignedSignatureTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPClearSignedSignatureTest.java
new file mode 100644
index 00000000..7c8f6294
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPClearSignedSignatureTest.java
@@ -0,0 +1,454 @@
+package org.bouncycastle.openpgp.test;
+
+import org.bouncycastle.bcpg.ArmoredInputStream;
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.BCPGOutputStream;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+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.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SignatureException;
+import java.util.Iterator;
+
+public class PGPClearSignedSignatureTest
+ extends SimpleTest
+{
+ byte[] publicKey = Base64.decode(
+ "mQELBEQh2+wBCAD26kte0hO6flr7Y2aetpPYutHY4qsmDPy+GwmmqVeCDkX+"
+ + "r1g7DuFbMhVeu0NkKDnVl7GsJ9VarYsFYyqu0NzLa9XS2qlTIkmJV+2/xKa1"
+ + "tzjn18fT/cnAWL88ZLCOWUr241aPVhLuIc6vpHnySpEMkCh4rvMaimnTrKwO"
+ + "42kgeDGd5cXfs4J4ovRcTbc4hmU2BRVsRjiYMZWWx0kkyL2zDVyaJSs4yVX7"
+ + "Jm4/LSR1uC/wDT0IJJuZT/gQPCMJNMEsVCziRgYkAxQK3OWojPSuv4rXpyd4"
+ + "Gvo6IbvyTgIskfpSkCnQtORNLIudQSuK7pW+LkL62N+ohuKdMvdxauOnAAYp"
+ + "tBNnZ2dnZ2dnZyA8Z2dnQGdnZ2c+iQE2BBMBAgAgBQJEIdvsAhsDBgsJCAcD"
+ + "AgQVAggDBBYCAwECHgECF4AACgkQ4M/Ier3f9xagdAf/fbKWBjLQM8xR7JkR"
+ + "P4ri8YKOQPhK+VrddGUD59/wzVnvaGyl9MZE7TXFUeniQq5iXKnm22EQbYch"
+ + "v2Jcxyt2H9yptpzyh4tP6tEHl1C887p2J4qe7F2ATua9CzVGwXQSUbKtj2fg"
+ + "UZP5SsNp25guhPiZdtkf2sHMeiotmykFErzqGMrvOAUThrO63GiYsRk4hF6r"
+ + "cQ01d+EUVpY/sBcCxgNyOiB7a84sDtrxnX5BTEZDTEj8LvuEyEV3TMUuAjx1"
+ + "7Eyd+9JtKzwV4v3hlTaWOvGro9nPS7YaPuG+RtufzXCUJPbPfTjTvtGOqvEz"
+ + "oztls8tuWA0OGHba9XfX9rfgorACAAM=");
+
+ byte[] secretKey = Base64.decode(
+ "lQOWBEQh2+wBCAD26kte0hO6flr7Y2aetpPYutHY4qsmDPy+GwmmqVeCDkX+"
+ + "r1g7DuFbMhVeu0NkKDnVl7GsJ9VarYsFYyqu0NzLa9XS2qlTIkmJV+2/xKa1"
+ + "tzjn18fT/cnAWL88ZLCOWUr241aPVhLuIc6vpHnySpEMkCh4rvMaimnTrKwO"
+ + "42kgeDGd5cXfs4J4ovRcTbc4hmU2BRVsRjiYMZWWx0kkyL2zDVyaJSs4yVX7"
+ + "Jm4/LSR1uC/wDT0IJJuZT/gQPCMJNMEsVCziRgYkAxQK3OWojPSuv4rXpyd4"
+ + "Gvo6IbvyTgIskfpSkCnQtORNLIudQSuK7pW+LkL62N+ohuKdMvdxauOnAAYp"
+ + "AAf+JCJJeAXEcrTVHotsrRR5idzmg6RK/1MSQUijwPmP7ZGy1BmpAmYUfbxn"
+ + "B56GvXyFV3Pbj9PgyJZGS7cY+l0BF4ZqN9USiQtC9OEpCVT5LVMCFXC/lahC"
+ + "/O3EkjQy0CYK+GwyIXa+Flxcr460L/Hvw2ZEXJZ6/aPdiR+DU1l5h99Zw8V1"
+ + "Y625MpfwN6ufJfqE0HLoqIjlqCfi1iwcKAK2oVx2SwnT1W0NwUUXjagGhD2s"
+ + "VzJVpLqhlwmS0A+RE9Niqrf80/zwE7QNDF2DtHxmMHJ3RY/pfu5u1rrFg9YE"
+ + "lmS60mzOe31CaD8Li0k5YCJBPnmvM9mN3/DWWprSZZKtmQQA96C2/VJF5EWm"
+ + "+/Yxi5J06dG6Bkz311Ui4p2zHm9/4GvTPCIKNpGx9Zn47YFD3tIg3fIBVPOE"
+ + "ktG38pEPx++dSSFF9Ep5UgmYFNOKNUVq3yGpatBtCQBXb1LQLAMBJCJ5TQmk"
+ + "68hMOEaqjMHSOa18cS63INgA6okb/ueAKIHxYQcEAP9DaXu5n9dZQw7pshbN"
+ + "Nu/T5IP0/D/wqM+W5r+j4P1N7PgiAnfKA4JjKrUgl8PGnI2qM/Qu+g3qK++c"
+ + "F1ESHasnJPjvNvY+cfti06xnJVtCB/EBOA2UZkAr//Tqa76xEwYAWRBnO2Y+"
+ + "KIVOT+nMiBFkjPTrNAD6fSr1O4aOueBhBAC6aA35IfjC2h5MYk8+Z+S4io2o"
+ + "mRxUZ/dUuS+kITvWph2e4DT28Xpycpl2n1Pa5dCDO1lRqe/5JnaDYDKqxfmF"
+ + "5tTG8GR4d4nVawwLlifXH5Ll7t5NcukGNMCsGuQAHMy0QHuAaOvMdLs5kGHn"
+ + "8VxfKEVKhVrXsvJSwyXXSBtMtUcRtBNnZ2dnZ2dnZyA8Z2dnQGdnZ2c+iQE2"
+ + "BBMBAgAgBQJEIdvsAhsDBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQ4M/I"
+ + "er3f9xagdAf/fbKWBjLQM8xR7JkRP4ri8YKOQPhK+VrddGUD59/wzVnvaGyl"
+ + "9MZE7TXFUeniQq5iXKnm22EQbYchv2Jcxyt2H9yptpzyh4tP6tEHl1C887p2"
+ + "J4qe7F2ATua9CzVGwXQSUbKtj2fgUZP5SsNp25guhPiZdtkf2sHMeiotmykF"
+ + "ErzqGMrvOAUThrO63GiYsRk4hF6rcQ01d+EUVpY/sBcCxgNyOiB7a84sDtrx"
+ + "nX5BTEZDTEj8LvuEyEV3TMUuAjx17Eyd+9JtKzwV4v3hlTaWOvGro9nPS7Ya"
+ + "PuG+RtufzXCUJPbPfTjTvtGOqvEzoztls8tuWA0OGHba9XfX9rfgorACAAA=");
+
+ String crOnlyMessage =
+ "\r"
+ + " hello world!\r"
+ + "\r"
+ + "- dash\r";
+
+ String nlOnlyMessage =
+ "\n"
+ + " hello world!\n"
+ + "\n"
+ + "- dash\n";
+
+ String crNlMessage =
+ "\r\n"
+ + " hello world!\r\n"
+ + "\r\n"
+ + "- dash\r\n";
+
+ String crOnlySignedMessage =
+ "-----BEGIN PGP SIGNED MESSAGE-----\r"
+ + "Hash: SHA256\r"
+ + "\r"
+ + "\r"
+ + " hello world!\r"
+ + "\r"
+ + "- - dash\r"
+ + "-----BEGIN PGP SIGNATURE-----\r"
+ + "Version: GnuPG v1.4.2.1 (GNU/Linux)\r"
+ + "\r"
+ + "iQEVAwUBRCNS8+DPyHq93/cWAQi6SwgAj3ItmSLr/sd/ixAQLW7/12jzEjfNmFDt\r"
+ + "WOZpJFmXj0fnMzTrOILVnbxHv2Ru+U8Y1K6nhzFSR7d28n31/XGgFtdohDEaFJpx\r"
+ + "Fl+KvASKIonnpEDjFJsPIvT1/G/eCPalwO9IuxaIthmKj0z44SO1VQtmNKxdLAfK\r"
+ + "+xTnXGawXS1WUE4CQGPM45mIGSqXcYrLtJkAg3jtRa8YRUn2d7b2BtmWH+jVaVuC\r"
+ + "hNrXYv7iHFOu25yRWhUQJisvdC13D/gKIPRvARXPgPhAC2kovIy6VS8tDoyG6Hm5\r"
+ + "dMgLEGhmqsgaetVq1ZIuBZj5S4j2apBJCDpF6GBfpBOfwIZs0Tpmlw==\r"
+ + "=84Nd\r"
+ + "-----END PGP SIGNATURE-----\r";
+
+
+ String nlOnlySignedMessage =
+ "-----BEGIN PGP SIGNED MESSAGE-----\n"
+ + "Hash: SHA256\n"
+ + "\n"
+ + "\n"
+ + " hello world!\n"
+ + "\n"
+ + "- - dash\n"
+ + "-----BEGIN PGP SIGNATURE-----\n"
+ + "Version: GnuPG v1.4.2.1 (GNU/Linux)\n"
+ + "\n"
+ + "iQEVAwUBRCNS8+DPyHq93/cWAQi6SwgAj3ItmSLr/sd/ixAQLW7/12jzEjfNmFDt\n"
+ + "WOZpJFmXj0fnMzTrOILVnbxHv2Ru+U8Y1K6nhzFSR7d28n31/XGgFtdohDEaFJpx\n"
+ + "Fl+KvASKIonnpEDjFJsPIvT1/G/eCPalwO9IuxaIthmKj0z44SO1VQtmNKxdLAfK\n"
+ + "+xTnXGawXS1WUE4CQGPM45mIGSqXcYrLtJkAg3jtRa8YRUn2d7b2BtmWH+jVaVuC\n"
+ + "hNrXYv7iHFOu25yRWhUQJisvdC13D/gKIPRvARXPgPhAC2kovIy6VS8tDoyG6Hm5\n"
+ + "dMgLEGhmqsgaetVq1ZIuBZj5S4j2apBJCDpF6GBfpBOfwIZs0Tpmlw==\n"
+ + "=84Nd\n"
+ + "-----END PGP SIGNATURE-----\n";
+
+ String crNlSignedMessage =
+ "-----BEGIN PGP SIGNED MESSAGE-----\r\n"
+ + "Hash: SHA256\r\n"
+ + "\r\n"
+ + "\r\n"
+ + " hello world!\r\n"
+ + "\r\n"
+ + "- - dash\r\n"
+ + "-----BEGIN PGP SIGNATURE-----\r\n"
+ + "Version: GnuPG v1.4.2.1 (GNU/Linux)\r\n"
+ + "\r\n"
+ + "iQEVAwUBRCNS8+DPyHq93/cWAQi6SwgAj3ItmSLr/sd/ixAQLW7/12jzEjfNmFDt\r\n"
+ + "WOZpJFmXj0fnMzTrOILVnbxHv2Ru+U8Y1K6nhzFSR7d28n31/XGgFtdohDEaFJpx\r\n"
+ + "Fl+KvASKIonnpEDjFJsPIvT1/G/eCPalwO9IuxaIthmKj0z44SO1VQtmNKxdLAfK\r\n"
+ + "+xTnXGawXS1WUE4CQGPM45mIGSqXcYrLtJkAg3jtRa8YRUn2d7b2BtmWH+jVaVuC\r\n"
+ + "hNrXYv7iHFOu25yRWhUQJisvdC13D/gKIPRvARXPgPhAC2kovIy6VS8tDoyG6Hm5\r\n"
+ + "dMgLEGhmqsgaetVq1ZIuBZj5S4j2apBJCDpF6GBfpBOfwIZs0Tpmlw==\r\n"
+ + "=84Nd\r"
+ + "-----END PGP SIGNATURE-----\r\n";
+
+ String crNlSignedMessageTrailingWhiteSpace =
+ "-----BEGIN PGP SIGNED MESSAGE-----\r\n"
+ + "Hash: SHA256\r\n"
+ + "\r\n"
+ + "\r\n"
+ + " hello world! \t\r\n"
+ + "\r\n"
+ + "- - dash\r\n"
+ + "-----BEGIN PGP SIGNATURE-----\r\n"
+ + "Version: GnuPG v1.4.2.1 (GNU/Linux)\r\n"
+ + "\r\n"
+ + "iQEVAwUBRCNS8+DPyHq93/cWAQi6SwgAj3ItmSLr/sd/ixAQLW7/12jzEjfNmFDt\r\n"
+ + "WOZpJFmXj0fnMzTrOILVnbxHv2Ru+U8Y1K6nhzFSR7d28n31/XGgFtdohDEaFJpx\r\n"
+ + "Fl+KvASKIonnpEDjFJsPIvT1/G/eCPalwO9IuxaIthmKj0z44SO1VQtmNKxdLAfK\r\n"
+ + "+xTnXGawXS1WUE4CQGPM45mIGSqXcYrLtJkAg3jtRa8YRUn2d7b2BtmWH+jVaVuC\r\n"
+ + "hNrXYv7iHFOu25yRWhUQJisvdC13D/gKIPRvARXPgPhAC2kovIy6VS8tDoyG6Hm5\r\n"
+ + "dMgLEGhmqsgaetVq1ZIuBZj5S4j2apBJCDpF6GBfpBOfwIZs0Tpmlw==\r\n"
+ + "=84Nd\r"
+ + "-----END PGP SIGNATURE-----\r\n";
+
+ public String getName()
+ {
+ return "PGPClearSignedSignature";
+ }
+
+ private void messageTest(
+ String message,
+ String type)
+ throws Exception
+ {
+ ArmoredInputStream aIn = new ArmoredInputStream(new ByteArrayInputStream(message.getBytes()));
+
+ String[] headers = aIn.getArmorHeaders();
+
+ if (headers == null || headers.length != 1)
+ {
+ fail("wrong number of headers found");
+ }
+
+ if (!"Hash: SHA256".equals(headers[0]))
+ {
+ fail("header value wrong: " + headers[0]);
+ }
+
+ //
+ // read the input, making sure we ingore the last newline.
+ //
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ int ch;
+
+ while ((ch = aIn.read()) >= 0 && aIn.isClearText())
+ {
+ bOut.write((byte)ch);
+ }
+
+ PGPPublicKeyRingCollection pgpRings = new PGPPublicKeyRingCollection(publicKey);
+
+ PGPObjectFactory pgpFact = new PGPObjectFactory(aIn);
+ PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
+ PGPSignature sig = p3.get(0);
+
+ sig.initVerify(pgpRings.getPublicKey(sig.getKeyID()), "BC");
+
+ ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
+ InputStream sigIn = new ByteArrayInputStream(bOut.toByteArray());
+ int lookAhead = readInputLine(lineOut, sigIn);
+
+ processLine(sig, lineOut.toByteArray());
+
+ if (lookAhead != -1)
+ {
+ do
+ {
+ lookAhead = readInputLine(lineOut, lookAhead, sigIn);
+
+ sig.update((byte)'\r');
+ sig.update((byte)'\n');
+
+ processLine(sig, lineOut.toByteArray());
+ }
+ while (lookAhead != -1);
+ }
+
+ if (!sig.verify())
+ {
+ fail("signature failed to verify in " + type);
+ }
+ }
+
+ private PGPSecretKey readSecretKey(
+ InputStream in)
+ throws IOException, PGPException
+ {
+ PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(in);
+
+ 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 void generateTest(
+ String message,
+ String type)
+ throws Exception
+ {
+ PGPSecretKey pgpSecKey = readSecretKey(new ByteArrayInputStream(secretKey));
+ PGPPrivateKey pgpPrivKey = pgpSecKey.extractPrivateKey("".toCharArray(), "BC");
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(pgpSecKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256, "BC");
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+
+ sGen.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, pgpPrivKey);
+
+ Iterator it = pgpSecKey.getPublicKey().getUserIDs();
+ if (it.hasNext())
+ {
+ spGen.setSignerUserID(false, (String)it.next());
+ sGen.setHashedSubpackets(spGen.generate());
+ }
+
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ ArmoredOutputStream aOut = new ArmoredOutputStream(bOut);
+ ByteArrayInputStream bIn = new ByteArrayInputStream(message.getBytes());
+
+ aOut.beginClearText(PGPUtil.SHA256);
+
+ //
+ // note the last \n in the file is ignored
+ //
+ ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
+ int lookAhead = readInputLine(lineOut, bIn);
+
+ processLine(aOut, sGen, lineOut.toByteArray());
+
+ if (lookAhead != -1)
+ {
+ do
+ {
+ lookAhead = readInputLine(lineOut, lookAhead, bIn);
+
+ sGen.update((byte)'\r');
+ sGen.update((byte)'\n');
+
+ processLine(aOut, sGen, lineOut.toByteArray());
+ }
+ while (lookAhead != -1);
+ }
+
+ aOut.endClearText();
+
+ BCPGOutputStream bcpgOut = new BCPGOutputStream(aOut);
+
+ sGen.generate().encode(bcpgOut);
+
+ aOut.close();
+
+ messageTest(new String(bOut.toByteArray()), type);
+ }
+
+ private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
+ throws IOException
+ {
+ bOut.reset();
+
+ int lookAhead = -1;
+ int ch;
+
+ while ((ch = fIn.read()) >= 0)
+ {
+ bOut.write(ch);
+ if (ch == '\r' || ch == '\n')
+ {
+ lookAhead = readPassedEOL(bOut, ch, fIn);
+ break;
+ }
+ }
+
+ return lookAhead;
+ }
+
+ private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn)
+ throws IOException
+ {
+ bOut.reset();
+
+ int ch = lookAhead;
+
+ do
+ {
+ bOut.write(ch);
+ if (ch == '\r' || ch == '\n')
+ {
+ lookAhead = readPassedEOL(bOut, ch, fIn);
+ break;
+ }
+ }
+ while ((ch = fIn.read()) >= 0);
+
+ return lookAhead;
+ }
+
+ private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
+ throws IOException
+ {
+ int lookAhead = fIn.read();
+
+ if (lastCh == '\r' && lookAhead == '\n')
+ {
+ bOut.write(lookAhead);
+ lookAhead = fIn.read();
+ }
+
+ return lookAhead;
+ }
+
+ private static void processLine(PGPSignature sig, byte[] line)
+ throws SignatureException, IOException
+ {
+ int length = getLengthWithoutWhiteSpace(line);
+ if (length > 0)
+ {
+ sig.update(line, 0, length);
+ }
+ }
+
+ private static void processLine(OutputStream aOut, PGPSignatureGenerator sGen, byte[] line)
+ throws SignatureException, IOException
+ {
+ int length = getLengthWithoutWhiteSpace(line);
+ if (length > 0)
+ {
+ sGen.update(line, 0, length);
+ }
+
+ aOut.write(line, 0, line.length);
+ }
+
+ private static int getLengthWithoutWhiteSpace(byte[] line)
+ {
+ int end = line.length - 1;
+
+ while (end >= 0 && isWhiteSpace(line[end]))
+ {
+ end--;
+ }
+
+ return end + 1;
+ }
+
+ private static boolean isWhiteSpace(byte b)
+ {
+ return b == '\r' || b == '\n' || b == '\t' || b == ' ';
+ }
+
+ public void performTest()
+ throws Exception
+ {
+ messageTest(crOnlySignedMessage, "\\r");
+ messageTest(nlOnlySignedMessage, "\\n");
+ messageTest(crNlSignedMessage, "\\r\\n");
+ messageTest(crNlSignedMessageTrailingWhiteSpace, "\\r\\n");
+
+ generateTest(nlOnlyMessage, "\\r");
+ generateTest(crOnlyMessage, "\\n");
+ generateTest(crNlMessage, "\\r\\n");
+ }
+
+ public static void main(
+ String[] args)
+ {
+ runTest(new PGPClearSignedSignatureTest());
+ }
+}
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPCompressionTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPCompressionTest.java
new file mode 100644
index 00000000..e5d74e20
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPCompressionTest.java
@@ -0,0 +1,143 @@
+package org.bouncycastle.openpgp.test;
+
+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.PGPObjectFactory;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.UncloseableOutputStream;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Security;
+
+public class PGPCompressionTest
+ extends SimpleTest
+{
+ public void performTest()
+ throws Exception
+ {
+ testCompression(PGPCompressedData.UNCOMPRESSED);
+ testCompression(PGPCompressedData.ZIP);
+ testCompression(PGPCompressedData.ZLIB);
+ testCompression(PGPCompressedData.BZIP2);
+
+ //
+ // new style - using stream close
+ //
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ PGPCompressedDataGenerator cPacket = new PGPCompressedDataGenerator(
+ PGPCompressedData.ZIP);
+
+ OutputStream out = cPacket.open(new UncloseableOutputStream(bOut), new byte[4]);
+
+ out.write("hello world! !dlrow olleh".getBytes());
+
+ out.close();
+
+ validateData(bOut.toByteArray());
+
+ try
+ {
+ out.close();
+ cPacket.close();
+ }
+ catch (Exception e)
+ {
+ fail("Redundant close() should be ignored");
+ }
+
+ //
+ // new style - using generator close
+ //
+ bOut = new ByteArrayOutputStream();
+ cPacket = new PGPCompressedDataGenerator(
+ PGPCompressedData.ZIP);
+
+ out = cPacket.open(new UncloseableOutputStream(bOut), new byte[4]);
+
+ out.write("hello world! !dlrow olleh".getBytes());
+
+ cPacket.close();
+
+ validateData(bOut.toByteArray());
+
+ try
+ {
+ out.close();
+ cPacket.close();
+ }
+ catch (Exception e)
+ {
+ fail("Redundant close() should be ignored");
+ }
+ }
+
+ private void validateData(byte[] data)
+ throws IOException, PGPException
+ {
+ PGPObjectFactory pgpFact = new PGPObjectFactory(data);
+ PGPCompressedData c1 = (PGPCompressedData)pgpFact.nextObject();
+ InputStream pIn = c1.getDataStream();
+
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ int ch;
+ while ((ch = pIn.read()) >= 0)
+ {
+ bOut.write(ch);
+ }
+
+ if (!areEqual(bOut.toByteArray(), "hello world! !dlrow olleh".getBytes()))
+ {
+ fail("compression test failed");
+ }
+ }
+
+ private void testCompression(
+ int type)
+ throws IOException, PGPException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ PGPCompressedDataGenerator cPacket = new PGPCompressedDataGenerator(type);
+
+ OutputStream out = cPacket.open(new UncloseableOutputStream(bOut));
+
+ out.write("hello world!".getBytes());
+
+ out.close();
+
+ PGPObjectFactory pgpFact = new PGPObjectFactory(bOut.toByteArray());
+ PGPCompressedData c1 = (PGPCompressedData)pgpFact.nextObject();
+ InputStream pIn = c1.getDataStream();
+
+ bOut.reset();
+
+ int ch;
+ while ((ch = pIn.read()) >= 0)
+ {
+ bOut.write(ch);
+ }
+
+ if (!areEqual(bOut.toByteArray(), "hello world!".getBytes()))
+ {
+ fail("compression test failed");
+ }
+ }
+
+ public String getName()
+ {
+ return "PGPCompressionTest";
+ }
+
+ public static void main(
+ String[] args)
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ runTest(new PGPCompressionTest());
+ }
+}
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPDSAElGamalTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPDSAElGamalTest.java
new file mode 100644
index 00000000..76ff2708
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPDSAElGamalTest.java
@@ -0,0 +1,552 @@
+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;
+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.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.UncloseableOutputStream;
+
+public class PGPDSAElGamalTest
+ 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 = null;
+
+ 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);
+ PGPPrivateKey pgpPrivKey = sKey.getSecretKey().extractPrivateKey(pass, "BC");
+
+ //
+ // signature generation
+ //
+ String data = "hello world!";
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ ByteArrayInputStream testIn = new ByteArrayInputStream(data.getBytes());
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(PGPPublicKey.DSA, PGPUtil.SHA1, "BC");
+
+ sGen.initSign(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.initVerify(pubKey, "BC");
+
+ 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;
+ PublicKey pKey = null;
+
+ Iterator it = pgpPub.getPublicKeys();
+ while (it.hasNext())
+ {
+ PGPPublicKey pgpKey = (PGPPublicKey)it.next();
+
+ if (pgpKey.getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT
+ || pgpKey.getAlgorithm() == PGPPublicKey.ELGAMAL_GENERAL)
+ {
+ pKey = pgpKey.getKey("BC");
+ pgpKeyID = pgpKey.getKeyID();
+ if (pgpKey.getBitStrength() != 1024)
+ {
+ fail("failed - key strength reported incorrectly.");
+ }
+
+ //
+ // verify the key
+ //
+
+ }
+ }
+
+ Cipher c = Cipher.getInstance("ElGamal/None/PKCS1Padding", "BC");
+
+ c.init(Cipher.ENCRYPT_MODE, pKey);
+
+ byte[] in = "hello world".getBytes();
+
+ byte[] out = c.doFinal(in);
+
+ pgpPrivKey = sKey.getSecretKey(pgpKeyID).extractPrivateKey(pass, "BC");
+
+ c.init(Cipher.DECRYPT_MODE, pgpPrivKey.getKey());
+
+ out = c.doFinal(out);
+
+ 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(pgpPrivKey, "BC");
+
+ 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(pgpPrivKey, "BC");
+
+ 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.initVerify(pgpPub.getPublicKey(), "BC");
+
+ 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(SymmetricKeyAlgorithmTags.TRIPLE_DES, new SecureRandom(), "BC");
+ PGPPublicKey puK = sKey.getSecretKey(pgpKeyID).getPublicKey();
+
+ cPk.addMethod(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(pass, "BC");
+
+ clear = encP.getDataStream(pgpPrivKey, "BC");
+
+ 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(SymmetricKeyAlgorithmTags.CAST5, random, "BC");
+
+ puK = elGamalKeyPair.getPublicKey();
+
+ cPk.addMethod(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(pgpPrivKey, "BC");
+
+ 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 = 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)
+ {
+ fail("exception: " + e.getMessage(), e.getUnderlyingException());
+ }
+ }
+
+ public String getName()
+ {
+ return "PGPDSAElGamalTest";
+ }
+
+ public static void main(
+ String[] args)
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ runTest(new PGPDSAElGamalTest());
+ }
+}
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPDSATest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPDSATest.java
new file mode 100644
index 00000000..e0ecba20
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPDSATest.java
@@ -0,0 +1,628 @@
+package org.bouncycastle.openpgp.test;
+
+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.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.UncloseableOutputStream;
+
+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;
+
+public class PGPDSATest
+ 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"
+ + "9EAhgCDkHkEcgilryTwd4zn0m4WzvpTJZSMBuY5MfbueletKyugZWDKwyCOc/j3o"
+ + "AduyWLDeWB5Ynj8jSUUUAdFXn/xU15dO0RbGGYC5uWwUB6L1Jx+n413F1cJa2stz"
+ + "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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(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1, "BC");
+
+ sGen.initSign(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.initVerify(pgpPubKey, "BC");
+
+ 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);
+
+ pubKey = pgpPub.getPublicKey();
+
+ //
+ // Read the private key
+ //
+ PGPSecretKeyRing sKey = new PGPSecretKeyRing(testPrivKey);
+ PGPPrivateKey pgpPrivKey = sKey.getSecretKey().extractPrivateKey(pass, "BC");
+
+ //
+ // 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.initVerify(pubKey, "BC");
+
+ 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(PGPPublicKey.DSA, PGPUtil.SHA1, "BC");
+
+ sGen.initSign(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.initVerify(pubKey, "BC");
+
+ 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);
+
+ 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);
+
+ 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);
+ pgpPrivKey = sKey.getSecretKey().extractPrivateKey(passPhrase, "BC");
+
+ byte[] bytes = pgpPrivKey.getKey().getEncoded();
+
+ //
+ // reading test - aes256 encrypted passphrase.
+ //
+ sKey = new PGPSecretKeyRing(aesSecretKey);
+ pgpPrivKey = sKey.getSecretKey().extractPrivateKey(pass, "BC");
+
+ bytes = pgpPrivKey.getKey().getEncoded();
+
+ //
+ // reading test - twofish encrypted passphrase.
+ //
+ sKey = new PGPSecretKeyRing(twofishSecretKey);
+ pgpPrivKey = sKey.getSecretKey().extractPrivateKey(pass, "BC");
+
+ 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 "PGPDSATest";
+ }
+
+ public static void main(
+ String[] args)
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ runTest(new PGPSignatureTest());
+ }
+}
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyRingTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyRingTest.java
new file mode 100644
index 00000000..d52f56ee
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyRingTest.java
@@ -0,0 +1,2610 @@
+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;
+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.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;
+
+public class PGPKeyRingTest
+ 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(
+ "mQGiBEBtfW8RBADfWjTxFedIbGBNVgh064D/OCf6ul7x4PGsCl+BkAyheYkr"
+ + "mVUsChmBKoeXaY+Fb85wwusXzyM/6JFK58Rg+vEb3Z19pue8Ixxq7cRtCtOA"
+ + "tOP1eKXLNtTRWJutvLkQmeOa19UZ6ziIq23aWuWKSq+KKMWek2GUnGycnx5M"
+ + "W0pn1QCg/39r9RKhY9cdKYqRcqsr9b2B/AsD/Ru24Q15Jmrsl9zZ6EC47J49"
+ + "iNW5sLQx1qf/mgfVWQTmU2j6gq4ND1OuK7+0OP/1yMOUpkjjcqxFgTnDAAoM"
+ + "hHDTzCv/aZzIzmMvgLsYU3aIMfbz+ojpuASMCMh+te01cEMjiPWwDtdWWOdS"
+ + "OSyX9ylzhO3PiNDks8R83onsacYpA/9WhTcg4bvkjaj66I7wGZkm3BmTxNSb"
+ + "pE4b5HZDh31rRYhY9tmrryCfFnU4BS2Enjj5KQe9zFv7pUBCBW2oFo8i8Osn"
+ + "O6fa1wVN4fBHC6wqWmmpnkFerNPkiC9V75KUFIfeWHmT3r2DVSO3dfdHDERA"
+ + "jFIAioMLjhaX6DnODF5KQrABh7QmU2FpIFB1bGxhYmhvdGxhIDxwc2FpQG15"
+ + "amF2YXdvcmxkLmNvbT6wAwP//4kAVwQQEQIAFwUCQG19bwcLCQgHAwIKAhkB"
+ + "BRsDAAAAAAoJEKXQf/RT99uYmfAAoMKxV5g2owIfmy2w7vSLvOQUpvvOAJ4n"
+ + "jB6xJot523rPAQW9itPoGGekirABZ7kCDQRAbX1vEAgA9kJXtwh/CBdyorrW"
+ + "qULzBej5UxE5T7bxbrlLOCDaAadWoxTpj0BV89AHxstDqZSt90xkhkn4DIO9"
+ + "ZekX1KHTUPj1WV/cdlJPPT2N286Z4VeSWc39uK50T8X8dryDxUcwYc58yWb/"
+ + "Ffm7/ZFexwGq01uejaClcjrUGvC/RgBYK+X0iP1YTknbzSC0neSRBzZrM2w4"
+ + "DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdXQ6MdGGzeMyEs"
+ + "tSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqVDNmWn6vQClCbAkbTCD1mpF1B"
+ + "n5x8vYlLIhkmuquiXsNV6TILOwACAgf9F7/nJHDayJ3pBVTTVSq2g5WKUXMg"
+ + "xxGKTvOahiVRcbO03w0pKAkH85COakVfe56sMYpWRl36adjNoKOxaciow74D"
+ + "1R5snY/hv/kBXPBkzo4UMkbANIVaZ0IcnLp+rkkXcDVbRCibZf8FfCY1zXbq"
+ + "d680UtEgRbv1D8wFBqfMt7kLsuf9FnIw6vK4DU06z5ZDg25RHGmswaDyY6Mw"
+ + "NGCrKGbHf9I/T7MMuhGF/in8UU8hv8uREOjseOqklG3/nsI1hD/MdUC7fzXi"
+ + "MRO4RvahLoeXOuaDkMYALdJk5nmNuCL1YPpbFGttI3XsK7UrP/Fhd8ND6Nro"
+ + "wCqrN6keduK+uLABh4kATAQYEQIADAUCQG19bwUbDAAAAAAKCRCl0H/0U/fb"
+ + "mC/0AJ4r1yvyu4qfOXlDgmVuCsvHFWo63gCfRIrCB2Jv/N1cgpmq0L8LGHM7"
+ + "G/KwAWeZAQ0EQG19owEIAMnavLYqR7ffaDPbbq+lQZvLCK/3uA0QlyngNyTa"
+ + "sDW0WC1/ryy2dx7ypOOCicjnPYfg3LP5TkYAGoMjxH5+xzM6xfOR+8/EwK1z"
+ + "N3A5+X/PSBDlYjQ9dEVKrvvc7iMOp+1K1VMf4Ug8Yah22Ot4eLGP0HRCXiv5"
+ + "vgdBNsAl/uXnBJuDYQmLrEniqq/6UxJHKHxZoS/5p13Cq7NfKB1CJCuJXaCE"
+ + "TW2do+cDpN6r0ltkF/r+ES+2L7jxyoHcvQ4YorJoDMlAN6xpIZQ8dNaTYP/n"
+ + "Mx/pDS3shUzbU+UYPQrreJLMF1pD+YWP5MTKaZTo+U/qPjDFGcadInhPxvh3"
+ + "1ssAEQEAAbABh7QuU2FuZGh5YSBQdWxsYWJob3RsYSA8cHNhbmRoeWFAbXlq"
+ + "YXZhd29ybGQuY29tPrADA///iQEtBBABAgAXBQJAbX2jBwsJCAcDAgoCGQEF"
+ + "GwMAAAAACgkQx87DL9gOvoeVUwgAkQXYiF0CxhKbDnuabAssnOEwJrutgCRO"
+ + "CJRQvIwTe3fe6hQaWn2Yowt8OQtNFiR8GfAY6EYxyFLKzZbAI/qtq5fHmN3e"
+ + "RSyNWe6d6e17hqZZL7kf2sVkyGTChHj7Jiuo7vWkdqT2MJN6BW5tS9CRH7Me"
+ + "D839STv+4mAAO9auGvSvicP6UEQikAyCy/ihoJxLQlspfbSNpi0vrUjCPT7N"
+ + "tWwfP0qF64i9LYkjzLqihnu+UareqOPhXcWnyFKrjmg4ezQkweNU2pdvCLbc"
+ + "W24FhT92ivHgpLyWTswXcqjhFjVlRr0+2sIz7v1k0budCsJ7PjzOoH0hJxCv"
+ + "sJQMlZR/e7ABZ7kBDQRAbX2kAQgAm5j+/LO2M4pKm/VUPkYuj3eefHkzjM6n"
+ + "KbvRZX1Oqyf+6CJTxQskUWKAtkzzKafPdS5Wg0CMqeXov+EFod4bPEYccszn"
+ + "cKd1U8NRwacbEpCvvvB84Yl2YwdWpDpkryyyLI4PbCHkeuwx9Dc2z7t4XDB6"
+ + "FyAJTMAkia7nzYa/kbeUO3c2snDb/dU7uyCsyKtTZyTyhTgtl/f9L03Bgh95"
+ + "y3mOUz0PimJ0Sg4ANczF4d04BpWkjLNVJi489ifWodPlHm1hag5drYekYpWJ"
+ + "+3g0uxs5AwayV9BcOkPKb1uU3EoYQw+nn0Kn314Nvx2M1tKYunuVNLEm0PhA"
+ + "/+B8PTq8BQARAQABsAGHiQEiBBgBAgAMBQJAbX2kBRsMAAAAAAoJEMfOwy/Y"
+ + "Dr6HkLoH/RBY8lvUv1r8IdTs5/fN8e/MnGeThLl+JrlYF/4t3tjXYIf5xUj/"
+ + "c9NdjreKYgHfMtrbVM08LlxUVQlkjuF3DIk5bVH9Blq8aXmyiwiM5GrCry+z"
+ + "WiqkpZze1G577C38mMJbHDwbqNCLALMzo+W2q04Avl5sniNnDNGbGz9EjhRg"
+ + "o7oS16KkkD6Ls4RnHTEZ0vyZOXodDHu+sk/2kzj8K07kKaM8rvR7aDKiI7HH"
+ + "1GxJz70fn1gkKuV2iAIIiU25bty+S3wr+5h030YBsUZF1qeKCdGOmpK7e9Of"
+ + "yv9U7rf6Z5l8q+akjqLZvej9RnxeH2Um7W+tGg2me482J+z6WOawAWc=");
+
+ 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(
+ "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"
+ + "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(
+ "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"
+ + "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");
+
+
+ // Key from http://www.angelfire.com/pr/pgpf/pgpoddities.html
+ private static final char[] v3KeyPass = "test@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
+ {
+ 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);
+
+ 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@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@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 JcaKeyFingerprintCalculator());
+
+ 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);
+ }
+
+ 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@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@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);
+
+ 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 JcaKeyFingerprintCalculator());
+
+ 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 JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(sec2pass1));
+ }
+ else if (k.getKeyID() == -6498553574938125416L
+ || k.getKeyID() == 59034765524361024L)
+ {
+ k.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").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);
+
+ 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 JcaKeyFingerprintCalculator());
+
+ Iterator it = pgpSec.getSecretKeys();
+ while (it.hasNext())
+ {
+ keyCount++;
+
+ PGPSecretKey k = (PGPSecretKey)it.next();
+
+ k.extractPrivateKey(sec3pass1, "BC");
+ }
+
+ 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 JcaKeyFingerprintCalculator());
+
+ Iterator it = pgpSec.getSecretKeys();
+ while (it.hasNext())
+ {
+ keyCount++;
+
+ PGPSecretKey k = (PGPSecretKey)it.next();
+
+ k.extractPrivateKey(sec3pass1, "BC");
+ }
+
+ if (keyCount != 2)
+ {
+ 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");
+ }
+ }
+
+ 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);
+
+ 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 JcaKeyFingerprintCalculator());
+
+ Iterator it = pgpSec.getSecretKeys();
+ while (it.hasNext())
+ {
+ keyCount++;
+
+ PGPSecretKey k = (PGPSecretKey)it.next();
+
+ k.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(sec5pass1));
+ }
+
+ if (keyCount != 2)
+ {
+ fail("wrong number of secret keys");
+ }
+ }
+
+ if (count != 1)
+ {
+ fail("wrong number of secret keyrings");
+ }
+ }
+
+ private boolean noIDEA()
+ {
+ try
+ {
+ Cipher.getInstance("IDEA", "BC");
+
+ return false;
+ }
+ catch (Exception e)
+ {
+ 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);
+ 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.initVerify(masterKey, "BC");
+
+ 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);
+
+ 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 JcaKeyFingerprintCalculator());
+
+ Iterator it = pgpSec.getSecretKeys();
+ while (it.hasNext())
+ {
+ keyCount++;
+
+ PGPSecretKey k = (PGPSecretKey)it.next();
+
+ k.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").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 JcaKeyFingerprintCalculator());
+
+ Iterator it = pgpSec.getSecretKeys();
+ while (it.hasNext())
+ {
+ keyCount++;
+
+ PGPSecretKey k = (PGPSecretKey)it.next();
+
+ PGPPrivateKey pKey = k.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").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 JcaKeyFingerprintCalculator());
+ 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);
+ 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 JcePBESecretKeyDecryptorBuilder().setProvider("BC").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.initVerify(vKey, "BC");
+
+ 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 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;
+ PGPPublicKey sKey = null;
+
+ Iterator it = pubRing.getPublicKeys();
+ while (it.hasNext())
+ {
+ PGPPublicKey pk = (PGPPublicKey)it.next();
+ if (pk.isMasterKey())
+ {
+ vKey = pk;
+ }
+ else
+ {
+ 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())
+ {
+ PGPSignature sig = (PGPSignature)sIt.next();
+
+ if (sig.getKeyID() == vKey.getKeyID()
+ && sig.getSignatureType() == PGPSignature.SUBKEY_BINDING)
+ {
+ sig.initVerify(vKey, "BC");
+
+ if (!sig.verifyCertification(vKey, sKey))
+ {
+ fail("failed to verify sub-key signature.");
+ }
+ }
+ }
+ }
+
+ private void test11()
+ throws Exception
+ {
+ PGPPublicKeyRing pubRing = new PGPPublicKeyRing(subKeyBindingKey);
+ 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
+ {
+ // 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();
+
+ 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(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,
+ 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(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");
+ }
+ }
+ }
+ }
+
+ private void testPublicKeyRingWithX509()
+ throws Exception
+ {
+ checkPublicKeyRingWithX509(pubWithX509);
+
+ PGPPublicKeyRing pubRing = new PGPPublicKeyRing(pubWithX509);
+
+ 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);
+
+ 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
+ {
+ 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);
+ 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();
+ rewrapTestV3();
+ rewrapTestMD5();
+ testPublicKeyRingWithX509();
+ testSecretKeyRingWithPersonalCertificate();
+ insertMasterTest();
+ testUmlaut();
+ }
+ catch (PGPException e)
+ {
+ if (((PGPException)e).getUnderlyingException() != null)
+ {
+ Exception ex = ((PGPException)e).getUnderlyingException();
+ fail("exception: " + ex, ex);
+ }
+ else
+ {
+ fail("exception: " + e, e);
+ }
+ }
+ }
+
+ public String getName()
+ {
+ return "PGPKeyRingTest";
+ }
+
+ public static void main(
+ String[] args)
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ runTest(new PGPKeyRingTest());
+ }
+}
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPMarkerTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPMarkerTest.java
new file mode 100644
index 00000000..f2b74245
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPMarkerTest.java
@@ -0,0 +1,105 @@
+package org.bouncycastle.openpgp.test;
+
+import org.bouncycastle.openpgp.PGPEncryptedDataList;
+import org.bouncycastle.openpgp.PGPMarker;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+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 PGPMarkerTest
+ implements Test
+{
+ private byte[] message1 = Base64.decode(
+ "qANQR1DBwU4DdrlXatQSHgoQCADWlhY3bWWaOTm4t2espRWPFQmETeinnieHce64"
+ + "lmEIFzaryEWeSdQc8XGfDzcb7sxq7b5b9Hm6OrACcCbSp2KGEJNG5kJmo2A16UPq"
+ + "JdK4xNelpJRh3KcJPv+N/9VJrMdj4C+DRnGNFg1hTQf3RKsX+ms2V0OBC5vGlOZY"
+ + "zX+XZz/7hl1PXVLN23u4npZI/1xETI2VtRoM76S6oykGXxMtT3+sGU1fAVEKVS45"
+ + "pyQHWbBqApkWrURq0xBqpVfDwOgGw09dJxt2igW9hjvNAd9tJiMGrMF5o2OLlub7"
+ + "c7FiK+dWLLcw+nx7Hl6FQmo9E8qyW8x1Cb78HjR/JXMgH/ngB/4gba6xX+s5TJkW"
+ + "H2Wpp5ePTw39EqHosUMrm05R+C0ha3EyyaJIvKj2WWmImKu5PWo1t37Pi6KHFNC3"
+ + "wsYJMRKnnNtd34luMTOgLpDcdgClzfp2p6EqHMoB7Uj3etlLmbN+vpGgz9qkLBRV"
+ + "7MpR1yE9qrZNeGgbkry6N31w5E7HoAHu5JNcwxgzbJoj2lI8uvs6Gf7fEoQOuAPE"
+ + "W/SGlfR2BdBPiJ1yErMElc2O8LVS0wTwwifHpEsMV+1ntl1EC5d052lo+6q7zNqD"
+ + "uYt1/2if6h9W9fe+S9mzr0ZAtxIN2ZGOFJJRnqzjDQ4siB9nnwr6YgvUVRSr/lQB"
+ + "hDTd0bmjyWacCt0PPMJWchO6A5tzqKUpTWSYibpdks80kLQogQHsJTZd/kpS0I6f"
+ + "gD0HYYlMssZwhg2J2TWwXDpDTgQ6mzFKbGSdOSk/deTJj2+EubzxaZcxZEocCJA8"
+ + "bppCj4kLBnCj1LjYx7A=");
+
+ private byte[] message2 = Base64.decode(
+ "qANQR1DBwU4DZlTzKj+E4aMQCADruFAojUIlHGcnswLIekvhbVnaHnbCt6Kp"
+ + "IL2zppmEIYJ9n1xCO1k+3Y5j9vNATbqCVWs1HD0aAL3PRI1eZ1l8GkIBCd2z"
+ + "tcZpSI/uyI/JCzVW2stCH0gpP2V7zcjk8HaIuBz4ZsyU9m7v6LwCDPB4CTrb"
+ + "Z5nn5Jm3eowonQsRL/3TpJtG+IjTaw29NbCBNNX8quM5LwfIsfWovqNv28r1"
+ + "aX8FsqoTRsWEfQ7dMV/swVGqv0PgKxqErdnZVJ2yOJqjLk+lBJT6zhqPijGV"
+ + "10pc68hdZxxLU1KZq25DAjS12xcAgagjRkOmYE/H1oEjGZlXfS4y/xQ7skHa"
+ + "HI+b04vECACTpQPwCXhxYiNWnf4XhJPONIGyrsXVtsTNwzOShFPmeUvpipP4"
+ + "HknakBkBuUY49xcffQogW/NlGCZnQOulDLE6fCH/krkSmI8WVP5Vhf6bM1Qm"
+ + "92dHZFoTrrcQ9NVGaCNHHWf7KXkNfKdTkE23LdggoVrVAzO4WcdqVc6s/or7"
+ + "jQYP9zXLeu8+GGFMxe/9FCtoIWbujGQHsdDEkCK4h+D44EVDPzbvWj39ZB4w"
+ + "hHoab8RLHd7njcrPeoCPdYkFVCKOSuLdxxYZDbbmgpISaafrafwefkkESeGu"
+ + "JzbNhmyS8zfOiejWzndaLYWUSE/sqISK9Pg+xKundnFPk04+AhIRyYEoUjG3"
+ + "LgGVyM49mrM8E7QwAGU0m/VCJLoOu+N74Z1rp1wFdA5yCllFlONNM4Czhd1D"
+ + "ZMyLFqGXiKlyVCPlUTN2uVisYQGr6iNGYSPxpKjwiAzdeeQBPOETG0vd3nTO"
+ + "MN4BMKcG+kRJd5FU72SRfmbGwPPjd1gts9xFvtj4Tvpkam8=");
+
+ public TestResult perform()
+ {
+ try
+ {
+ //
+ // test encrypted message
+ //
+ PGPObjectFactory pgpFact = new PGPObjectFactory(message1);
+
+ Object o;
+
+ if (pgpFact.nextObject() instanceof PGPMarker)
+ {
+ if (pgpFact.nextObject() instanceof PGPEncryptedDataList)
+ {
+ return new SimpleTestResult(true, getName() + ": Okay");
+ }
+ else
+ {
+ return new SimpleTestResult(false, getName() + ": error processing after marker.");
+ }
+ }
+
+ pgpFact = new PGPObjectFactory(message2);
+
+ if (pgpFact.nextObject() instanceof PGPMarker)
+ {
+ if (pgpFact.nextObject() instanceof PGPEncryptedDataList)
+ {
+ return new SimpleTestResult(true, getName() + ": Okay");
+ }
+ else
+ {
+ return new SimpleTestResult(false, getName() + ": error processing after marker.");
+ }
+ }
+
+ return new SimpleTestResult(false, getName() + ": marker not found");
+ }
+ catch (Exception e)
+ {
+ return new SimpleTestResult(false, getName() + ": exception - " + e.toString());
+ }
+ }
+
+ public String getName()
+ {
+ return "PGPMarkerTest";
+ }
+
+ public static void main(
+ String[] args)
+ {
+ Test test = new PGPMarkerTest();
+ TestResult result = test.perform();
+
+ System.out.println(result.toString());
+ }
+}
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPNoPrivateKeyTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPNoPrivateKeyTest.java
new file mode 100644
index 00000000..ed5f88f6
--- /dev/null
+++ b/pg/src/test/java/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/pg/src/test/java/org/bouncycastle/openpgp/test/PGPPBETest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPPBETest.java
new file mode 100644
index 00000000..0aa6570f
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPPBETest.java
@@ -0,0 +1,396 @@
+package org.bouncycastle.openpgp.test;
+
+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.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.UncloseableOutputStream;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Date;
+
+public class PGPPBETest
+ 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(pass, "BC");
+
+ 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(pass, "BC");
+
+ 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(PGPEncryptedData.CAST5, new SecureRandom(), "BC");
+
+ cPk.addMethod(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(PGPEncryptedData.CAST5, new SecureRandom(), "BC");
+
+ cPk.addMethod(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(PGPEncryptedData.CAST5, rand, "BC");
+
+ cPk.addMethod(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(PGPEncryptedData.CAST5, true, rand, "BC");
+
+ cPk.addMethod(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("password".toCharArray(), "BC");
+
+ 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(PGPEncryptedData.CAST5, true, rand, "BC");
+
+ cPk.addMethod(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 "PGPPBETest";
+ }
+
+ public static void main(
+ String[] args)
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ runTest(new PGPPBETest());
+ }
+}
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPPacketTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPPacketTest.java
new file mode 100644
index 00000000..c310b871
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPPacketTest.java
@@ -0,0 +1,103 @@
+package org.bouncycastle.openpgp.test;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.UncloseableOutputStream;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Security;
+import java.util.Date;
+import java.util.Random;
+
+public class PGPPacketTest
+ extends SimpleTest
+{
+ private static int MAX = 32000;
+
+ private void readBackTest(
+ PGPLiteralDataGenerator generator)
+ throws IOException
+ {
+ Random rand = new Random();
+ byte[] buf = new byte[MAX];
+
+ rand.nextBytes(buf);
+
+ for (int i = 1; i <= 200; i++)
+ {
+ bufferTest(generator, buf, i);
+ }
+
+ bufferTest(generator, buf, 8382);
+ bufferTest(generator, buf, 8383);
+ bufferTest(generator, buf, 8384);
+ bufferTest(generator, buf, 8385);
+
+ for (int i = 200; i < MAX; i += 100)
+ {
+ bufferTest(generator, buf, i);
+ }
+ }
+
+ private void bufferTest(
+ PGPLiteralDataGenerator generator,
+ byte[] buf,
+ int i)
+ throws IOException
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ OutputStream out = generator.open(
+ new UncloseableOutputStream(bOut),
+ PGPLiteralData.BINARY,
+ PGPLiteralData.CONSOLE,
+ i,
+ new Date());
+
+ out.write(buf, 0, i);
+
+ generator.close();
+
+ PGPObjectFactory fact = new PGPObjectFactory(bOut.toByteArray());
+ PGPLiteralData data = (PGPLiteralData)fact.nextObject();
+ InputStream in = data.getInputStream();
+
+ for (int count = 0; count != i; count++)
+ {
+ if (in.read() != (buf[count] & 0xff))
+ {
+ fail("failed readback test - length = " + i);
+ }
+ }
+ }
+
+ public void performTest()
+ throws IOException
+ {
+ PGPLiteralDataGenerator oldGenerator = new PGPLiteralDataGenerator(true);
+
+ readBackTest(oldGenerator);
+
+ PGPLiteralDataGenerator newGenerator = new PGPLiteralDataGenerator(false);
+
+ readBackTest(newGenerator);
+ }
+
+ public String getName()
+ {
+ return "PGPPacketTest";
+ }
+
+ public static void main(
+ String[] args)
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ runTest(new PGPPacketTest());
+ }
+}
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPRSATest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPRSATest.java
new file mode 100644
index 00000000..a61681c6
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPRSATest.java
@@ -0,0 +1,1471 @@
+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;
+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.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;
+
+public class PGPRSATest
+ 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);
+
+ PGPPublicKey pubKey = pgpPub.getPublicKey();
+
+ if (!areEqual(pubKey.getFingerprint(), Hex.decode("4FFB9F0884266C715D1CEAC804A3BBFA")))
+ {
+ fail("version 3 fingerprint test failed");
+ }
+
+ //
+ // version 4
+ //
+ pgpPub = new PGPPublicKeyRing(testPubKey);
+
+ 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(SymmetricKeyAlgorithmTags.AES_128, true, new SecureRandom(), "BC");
+
+ encGen.addMethod(pgpPubKey);
+
+ encGen.addMethod("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(pgpPrivKey, "BC");
+
+ 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("password".toCharArray(), "BC");
+
+ 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);
+
+ 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.initVerify(pubKey, "BC");
+
+ 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 JcaKeyFingerprintCalculator());
+ PGPSecretKeyRing pgpSec = new PGPSecretKeyRing(testPrivKey, new JcaKeyFingerprintCalculator());
+
+ PGPPublicKey pubKey = pgpPub.getPublicKey();
+
+ PGPUserAttributeSubpacketVectorGenerator vGen = new PGPUserAttributeSubpacketVectorGenerator();
+
+ vGen.setImageAttribute(ImageAttribute.JPEG, jpegImage);
+
+ PGPUserAttributeSubpacketVector uVec = vGen.generate();
+
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1, "BC");
+
+ sGen.initSign(PGPSignature.POSITIVE_CERTIFICATION, pgpSec.getSecretKey().extractPrivateKey(pass, "BC"));
+
+ 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.initVerify(pubKey, "BC");
+
+ 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@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@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
+ {
+ PublicKey pubKey = null;
+
+ //
+ // Read the public key
+ //
+ PGPPublicKeyRing pgpPub = new PGPPublicKeyRing(testPubKey);
+
+ 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.initVerify(pgpPub.getPublicKey(), "BC");
+
+ 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);
+ 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 JcaKeyFingerprintCalculator());
+ PGPPrivateKey pgpPrivKey = pgpPriv.getSecretKey().extractPrivateKey(passP, "BC");
+
+ //
+ // 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 JcaKeyFingerprintCalculator());
+ PGPPrivateKey pgpPrivKey = pgpPriv.getSecretKey().extractPrivateKey(pass, "BC");
+
+ //
+ // 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);
+
+ 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.initVerify(pgpPub.getPublicKey(ops.getKeyID()), "BC");
+
+ 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 JcaKeyFingerprintCalculator());
+
+ //
+ // 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);
+
+ PGPEncryptedDataList encList = (PGPEncryptedDataList)pgpF.nextObject();
+
+ PGPPublicKeyEncryptedData encP = (PGPPublicKeyEncryptedData)encList.get(0);
+
+ pgpPrivKey = pgpPriv.getSecretKey(encP.getKeyID()).extractPrivateKey(pass, "BC");
+
+ InputStream clear = encP.getDataStream(pgpPrivKey, "BC");
+
+ 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");
+ }
+
+ //
+ // encrypt - short message
+ //
+ byte[] shortText = { (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o' };
+
+ ByteArrayOutputStream cbOut = new ByteArrayOutputStream();
+ PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(SymmetricKeyAlgorithmTags.CAST5, new SecureRandom(), "BC");
+ PGPPublicKey puK = pgpPriv.getSecretKey(encP.getKeyID()).getPublicKey();
+
+ cPk.addMethod(puK);
+
+ OutputStream cOut = cPk.open(new UncloseableOutputStream(cbOut), shortText.length);
+
+ cOut.write(shortText);
+
+ cOut.close();
+
+ pgpF = new PGPObjectFactory(cbOut.toByteArray());
+
+ encList = (PGPEncryptedDataList)pgpF.nextObject();
+
+ encP = (PGPPublicKeyEncryptedData)encList.get(0);
+
+ pgpPrivKey = pgpPriv.getSecretKey(encP.getKeyID()).extractPrivateKey(pass, "BC");
+
+ if (encP.getSymmetricAlgorithm(pgpPrivKey, "BC") != SymmetricKeyAlgorithmTags.CAST5)
+ {
+ fail("symmetric algorithm mismatch");
+ }
+
+ clear = encP.getDataStream(pgpPrivKey, "BC");
+
+ 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(SymmetricKeyAlgorithmTags.CAST5, new SecureRandom(), "BC");
+ puK = pgpPriv.getSecretKey(encP.getKeyID()).getPublicKey();
+
+ cPk.addMethod(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(pass, "BC");
+
+ clear = encP.getDataStream(pgpPrivKey, "BC");
+
+ 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);
+ Object o;
+
+ while ((o = pgpFact.nextObject()) != null)
+ {
+ // System.out.println(o);
+ }
+
+ //
+ // key pair generation - CAST5 encryption
+ //
+ char[] passPhrase = "hello".toCharArray();
+
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+
+ kpg.initialize(1024);
+
+ KeyPair kp = kpg.generateKeyPair();
+
+ PGPSecretKey secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, PublicKeyAlgorithmTags.RSA_GENERAL, kp.getPublic(), kp.getPrivate(), new Date(), "fred", SymmetricKeyAlgorithmTags.CAST5, passPhrase, null, null, new SecureRandom(), "BC");
+
+ PGPPublicKey key = secretKey.getPublicKey();
+
+ it = key.getUserIDs();
+
+ uid = (String)it.next();
+
+ it = key.getSignaturesForID(uid);
+
+ sig = (PGPSignature)it.next();
+
+ sig.initVerify(key, "BC");
+
+ if (!sig.verifyCertification(uid, key))
+ {
+ fail("failed to verify certification");
+ }
+
+ pgpPrivKey = secretKey.extractPrivateKey(passPhrase, "BC");
+
+ 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(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1, "BC");
+
+ sGen.initSign(PGPSignature.KEY_REVOCATION, secretKey.extractPrivateKey(passPhrase, "BC"));
+
+ sig = sGen.generateCertification(key);
+
+ key = PGPPublicKey.addCertification(key, sig);
+
+ keyEnc = key.getEncoded();
+
+ PGPPublicKeyRing tmpRing = new PGPPublicKeyRing(keyEnc);
+
+ key = tmpRing.getPublicKey();
+
+ Iterator sgIt = key.getSignaturesOfType(PGPSignature.KEY_REVOCATION);
+
+ sig = (PGPSignature)sgIt.next();
+
+ sig.initVerify(key, "BC");
+
+ if (!sig.verifyCertification(key))
+ {
+ fail("failed to verify revocation certification");
+ }
+
+ //
+ // use of PGPKeyPair
+ //
+ PGPKeyPair pgpKp = new JcaPGPKeyPair(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, PublicKeyAlgorithmTags.RSA_GENERAL, kp.getPublic(), kp.getPrivate(), new Date(), "fred", SymmetricKeyAlgorithmTags.AES_256, passPhrase, null, null, new SecureRandom(), "BC");
+
+ secretKey.extractPrivateKey(passPhrase, "BC");
+
+ secretKey.encode(new ByteArrayOutputStream());
+
+ //
+ // secret key password changing.
+ //
+ String newPass = "newPass";
+
+ secretKey = PGPSecretKey.copyWithNewPassword(secretKey, passPhrase, newPass.toCharArray(), secretKey.getKeyEncryptionAlgorithm(), new SecureRandom(), "BC");
+
+ secretKey.extractPrivateKey(newPass.toCharArray(), "BC");
+
+ 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.initVerify(key, "BC");
+
+ if (!sig.verifyCertification(uid, key))
+ {
+ fail("failed to verify certification");
+ }
+
+ pgpPrivKey = secretKey.extractPrivateKey(newPass.toCharArray(), "BC");
+
+ //
+ // signature generation
+ //
+ String data = "hello world!";
+
+ bOut = new ByteArrayOutputStream();
+
+ ByteArrayInputStream testIn = new ByteArrayInputStream(data.getBytes());
+
+ sGen = new PGPSignatureGenerator(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1, "BC");
+
+ sGen.initSign(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.initVerify(secretKey.getPublicKey(), "BC");
+
+ 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(PGPPublicKey.RSA_GENERAL, PGPUtil.SHA1, "BC");
+
+ sGen.initSign(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.initVerify(secretKey.getPublicKey(), "BC");
+
+ 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);
+
+ secretKey = pgpPriv.getSecretKey();
+
+ pgpPrivKey = secretKey.extractPrivateKey(pgp8Pass, "BC");
+
+ //
+ // expiry
+ //
+ testExpiry(expiry60and30daysSig13Key, 60, 30);
+
+ fingerPrintTest();
+ existingEmbeddedJpegTest();
+ embeddedJpegTest();
+ sigsubpacketTest();
+ multipleExpiryTest();
+ }
+
+ private void testExpiry(
+ byte[] encodedRing,
+ int masterDays,
+ int subKeyDays)
+ throws Exception
+ {
+ PGPPublicKeyRing pubRing = new PGPPublicKeyRing(encodedRing);
+ 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()
+ {
+ try
+ {
+ Cipher.getInstance("IDEA", "BC");
+
+ return false;
+ }
+ catch (Exception e)
+ {
+ return true;
+ }
+ }
+
+ public String getName()
+ {
+ return "PGPRSATest";
+ }
+
+ public static void main(
+ String[] args)
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ runTest(new PGPRSATest());
+ }
+}
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPSignatureTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPSignatureTest.java
new file mode 100644
index 00000000..d49c88ae
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPSignatureTest.java
@@ -0,0 +1,780 @@
+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;
+import org.bouncycastle.bcpg.SignatureSubpacketTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.bcpg.sig.KeyFlags;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.PGPException;
+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.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.PGPV3SignatureGenerator;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.io.Streams;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.UncloseableOutputStream;
+
+public class PGPSignatureTest
+ extends SimpleTest
+{
+ private static final int[] NO_PREFERENCES = null;
+ private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[] { SymmetricKeyAlgorithmTags.AES_128, SymmetricKeyAlgorithmTags.TRIPLE_DES };
+ private static final int[] PREFERRED_HASH_ALGORITHMS = new int[] { HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA256 };
+ private static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[] { CompressionAlgorithmTags.ZLIB };
+
+ private static final int TEST_EXPIRATION_TIME = 10000;
+ private static final String TEST_USER_ID = "test user id";
+ private static final byte[] TEST_DATA = "hello world!\nhello world!\n".getBytes();
+ private static final byte[] TEST_DATA_WITH_CRLF = "hello world!\r\nhello world!\r\n".getBytes();
+
+ byte[] dsaKeyRing = 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==");
+
+ char[] dsaPass = "hello world".toCharArray();
+
+ byte[] rsaKeyRing = 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[] rsaPass = "2002 Buffalo Sabres".toCharArray();
+
+ byte[] nullPacketsSubKeyBinding = Base64.decode(
+ "iDYEGBECAAAAACp9AJ9PlJCrFpi+INwG7z61eku2Wg1HaQCgl33X5Egj+Kf7F9CXIWj2iFCvQDo=");
+
+ public void performTest()
+ throws Exception
+ {
+ //
+ // RSA tests
+ //
+ PGPSecretKeyRing pgpPriv = new PGPSecretKeyRing(rsaKeyRing);
+ PGPSecretKey secretKey = pgpPriv.getSecretKey();
+ PGPPrivateKey pgpPrivKey = secretKey.extractPrivateKey(rsaPass, "BC");
+
+ try
+ {
+ testSig(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1, secretKey.getPublicKey(), pgpPrivKey);
+
+ fail("RSA wrong key test failed.");
+ }
+ catch (PGPException e)
+ {
+ // expected
+ }
+
+ try
+ {
+ testSigV3(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1, secretKey.getPublicKey(), pgpPrivKey);
+
+ fail("RSA V3 wrong key test failed.");
+ }
+ catch (PGPException e)
+ {
+ // expected
+ }
+
+ //
+ // certifications
+ //
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1, "BC");
+
+ sGen.initSign(PGPSignature.KEY_REVOCATION, pgpPrivKey);
+
+ PGPSignature sig = sGen.generateCertification(secretKey.getPublicKey());
+
+ sig.initVerify(secretKey.getPublicKey(), "BC");
+
+ if (!sig.verifyCertification(secretKey.getPublicKey()))
+ {
+ fail("revocation verification failed.");
+ }
+
+ PGPSecretKeyRing pgpDSAPriv = new PGPSecretKeyRing(dsaKeyRing);
+ PGPSecretKey secretDSAKey = pgpDSAPriv.getSecretKey();
+ PGPPrivateKey pgpPrivDSAKey = secretDSAKey.extractPrivateKey(dsaPass, "BC");
+
+ sGen = new PGPSignatureGenerator(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1, "BC");
+
+ sGen.initSign(PGPSignature.SUBKEY_BINDING, pgpPrivDSAKey);
+
+ PGPSignatureSubpacketGenerator unhashedGen = new PGPSignatureSubpacketGenerator();
+ PGPSignatureSubpacketGenerator hashedGen = new PGPSignatureSubpacketGenerator();
+
+ hashedGen.setSignatureExpirationTime(false, TEST_EXPIRATION_TIME);
+ hashedGen.setSignerUserID(true, TEST_USER_ID);
+ hashedGen.setPreferredCompressionAlgorithms(false, PREFERRED_COMPRESSION_ALGORITHMS);
+ hashedGen.setPreferredHashAlgorithms(false, PREFERRED_HASH_ALGORITHMS);
+ hashedGen.setPreferredSymmetricAlgorithms(false, PREFERRED_SYMMETRIC_ALGORITHMS);
+
+ sGen.setHashedSubpackets(hashedGen.generate());
+ sGen.setUnhashedSubpackets(unhashedGen.generate());
+
+ sig = sGen.generateCertification(secretDSAKey.getPublicKey(), secretKey.getPublicKey());
+
+ byte[] sigBytes = sig.getEncoded();
+
+ PGPObjectFactory f = new PGPObjectFactory(sigBytes);
+
+ sig = ((PGPSignatureList) f.nextObject()).get(0);
+
+ sig.initVerify(secretDSAKey.getPublicKey(), "BC");
+
+ if (!sig.verifyCertification(secretDSAKey.getPublicKey(), secretKey.getPublicKey()))
+ {
+ fail("subkey binding verification failed.");
+ }
+
+ PGPSignatureSubpacketVector hashedPcks = sig.getHashedSubPackets();
+ PGPSignatureSubpacketVector unhashedPcks = sig.getUnhashedSubPackets();
+
+ if (hashedPcks.size() != 6)
+ {
+ fail("wrong number of hashed packets found.");
+ }
+
+ if (unhashedPcks.size() != 1)
+ {
+ fail("wrong number of unhashed packets found.");
+ }
+
+ if (!hashedPcks.getSignerUserID().equals(TEST_USER_ID))
+ {
+ fail("test userid not matching");
+ }
+
+ if (hashedPcks.getSignatureExpirationTime() != TEST_EXPIRATION_TIME)
+ {
+ fail("test signature expiration time not matching");
+ }
+
+ if (unhashedPcks.getIssuerKeyID() != secretDSAKey.getKeyID())
+ {
+ fail("wrong issuer key ID found in certification");
+ }
+
+ int[] prefAlgs = hashedPcks.getPreferredCompressionAlgorithms();
+ preferredAlgorithmCheck("compression", PREFERRED_COMPRESSION_ALGORITHMS, prefAlgs);
+
+ prefAlgs = hashedPcks.getPreferredHashAlgorithms();
+ preferredAlgorithmCheck("hash", PREFERRED_HASH_ALGORITHMS, prefAlgs);
+
+ prefAlgs = hashedPcks.getPreferredSymmetricAlgorithms();
+ preferredAlgorithmCheck("symmetric", PREFERRED_SYMMETRIC_ALGORITHMS, prefAlgs);
+
+ int[] criticalHashed = hashedPcks.getCriticalTags();
+
+ if (criticalHashed.length != 1)
+ {
+ fail("wrong number of critical packets found.");
+ }
+
+ if (criticalHashed[0] != SignatureSubpacketTags.SIGNER_USER_ID)
+ {
+ fail("wrong critical packet found in tag list.");
+ }
+
+ //
+ // no packets passed
+ //
+ sGen = new PGPSignatureGenerator(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1, "BC");
+
+ sGen.initSign(PGPSignature.SUBKEY_BINDING, pgpPrivDSAKey);
+
+ sGen.setHashedSubpackets(null);
+ sGen.setUnhashedSubpackets(null);
+
+ sig = sGen.generateCertification(TEST_USER_ID, secretKey.getPublicKey());
+
+ sig.initVerify(secretDSAKey.getPublicKey(), "BC");
+
+ if (!sig.verifyCertification(TEST_USER_ID, secretKey.getPublicKey()))
+ {
+ fail("subkey binding verification failed.");
+ }
+
+ hashedPcks = sig.getHashedSubPackets();
+
+ if (hashedPcks.size() != 1)
+ {
+ fail("found wrong number of hashed packets");
+ }
+
+ unhashedPcks = sig.getUnhashedSubPackets();
+
+ if (unhashedPcks.size() != 1)
+ {
+ fail("found wrong number of unhashed packets");
+ }
+
+ try
+ {
+ sig.verifyCertification(secretKey.getPublicKey());
+
+ fail("failed to detect non-key signature.");
+ }
+ catch (PGPException e)
+ {
+ // expected
+ }
+
+ //
+ // override hash packets
+ //
+ sGen = new PGPSignatureGenerator(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1, "BC");
+
+ sGen.initSign(PGPSignature.SUBKEY_BINDING, pgpPrivDSAKey);
+
+ hashedGen = new PGPSignatureSubpacketGenerator();
+
+ hashedGen.setSignatureCreationTime(false, new Date(0L));
+
+ sGen.setHashedSubpackets(hashedGen.generate());
+
+ sGen.setUnhashedSubpackets(null);
+
+ sig = sGen.generateCertification(TEST_USER_ID, secretKey.getPublicKey());
+
+ sig.initVerify(secretDSAKey.getPublicKey(), "BC");
+
+ if (!sig.verifyCertification(TEST_USER_ID, secretKey.getPublicKey()))
+ {
+ fail("subkey binding verification failed.");
+ }
+
+ hashedPcks = sig.getHashedSubPackets();
+
+ if (hashedPcks.size() != 1)
+ {
+ fail("found wrong number of hashed packets in override test");
+ }
+
+ if (!hashedPcks.hasSubpacket(SignatureSubpacketTags.CREATION_TIME))
+ {
+ fail("hasSubpacket test for creation time failed");
+ }
+
+ if (!hashedPcks.getSignatureCreationTime().equals(new Date(0L)))
+ {
+ fail("creation of overriden date failed.");
+ }
+
+ prefAlgs = hashedPcks.getPreferredCompressionAlgorithms();
+ preferredAlgorithmCheck("compression", NO_PREFERENCES, prefAlgs);
+
+ prefAlgs = hashedPcks.getPreferredHashAlgorithms();
+ preferredAlgorithmCheck("hash", NO_PREFERENCES, prefAlgs);
+
+ prefAlgs = hashedPcks.getPreferredSymmetricAlgorithms();
+ preferredAlgorithmCheck("symmetric", NO_PREFERENCES, prefAlgs);
+
+ if (hashedPcks.getKeyExpirationTime() != 0)
+ {
+ fail("unexpected key expiration time found");
+ }
+
+ if (hashedPcks.getSignatureExpirationTime() != 0)
+ {
+ fail("unexpected signature expiration time found");
+ }
+
+ if (hashedPcks.getSignerUserID() != null)
+ {
+ fail("unexpected signer user ID found");
+ }
+
+ criticalHashed = hashedPcks.getCriticalTags();
+
+ if (criticalHashed.length != 0)
+ {
+ fail("critical packets found when none expected");
+ }
+
+ unhashedPcks = sig.getUnhashedSubPackets();
+
+ if (unhashedPcks.size() != 1)
+ {
+ fail("found wrong number of unhashed packets in override test");
+ }
+
+ //
+ // general signatures
+ //
+ testSig(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA256, secretKey.getPublicKey(), pgpPrivKey);
+ testSig(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA384, secretKey.getPublicKey(), pgpPrivKey);
+ testSig(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA512, secretKey.getPublicKey(), pgpPrivKey);
+ testSigV3(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1, secretKey.getPublicKey(), pgpPrivKey);
+ testTextSig(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1, secretKey.getPublicKey(), pgpPrivKey, TEST_DATA_WITH_CRLF, TEST_DATA_WITH_CRLF);
+ testTextSig(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1, secretKey.getPublicKey(), pgpPrivKey, TEST_DATA, TEST_DATA_WITH_CRLF);
+ testTextSigV3(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1, secretKey.getPublicKey(), pgpPrivKey, TEST_DATA_WITH_CRLF, TEST_DATA_WITH_CRLF);
+ testTextSigV3(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1, secretKey.getPublicKey(), pgpPrivKey, TEST_DATA, TEST_DATA_WITH_CRLF);
+
+ //
+ // DSA Tests
+ //
+ pgpPriv = new PGPSecretKeyRing(dsaKeyRing);
+ secretKey = pgpPriv.getSecretKey();
+ pgpPrivKey = secretKey.extractPrivateKey(dsaPass, "BC");
+
+ try
+ {
+ testSig(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1, secretKey.getPublicKey(), pgpPrivKey);
+
+ fail("DSA wrong key test failed.");
+ }
+ catch (PGPException e)
+ {
+ // expected
+ }
+
+ try
+ {
+ testSigV3(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1, secretKey.getPublicKey(), pgpPrivKey);
+
+ fail("DSA V3 wrong key test failed.");
+ }
+ catch (PGPException e)
+ {
+ // expected
+ }
+
+ testSig(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1, secretKey.getPublicKey(), pgpPrivKey);
+ testSigV3(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1, secretKey.getPublicKey(), pgpPrivKey);
+ testTextSig(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1, secretKey.getPublicKey(), pgpPrivKey, TEST_DATA_WITH_CRLF, TEST_DATA_WITH_CRLF);
+ testTextSig(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1, secretKey.getPublicKey(), pgpPrivKey, TEST_DATA, TEST_DATA_WITH_CRLF);
+ testTextSigV3(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1, secretKey.getPublicKey(), pgpPrivKey, TEST_DATA_WITH_CRLF, TEST_DATA_WITH_CRLF);
+ testTextSigV3(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1, secretKey.getPublicKey(), pgpPrivKey, TEST_DATA, TEST_DATA_WITH_CRLF);
+
+ // special cases
+ //
+ testMissingSubpackets(nullPacketsSubKeyBinding);
+
+ testMissingSubpackets(generateV3BinarySig(pgpPrivKey, PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1));
+
+ // keyflags
+ testKeyFlagsValues();
+ }
+
+ private void testKeyFlagsValues()
+ {
+ checkValue(KeyFlags.CERTIFY_OTHER, 0x01);
+ checkValue(KeyFlags.SIGN_DATA, 0x02);
+ checkValue(KeyFlags.ENCRYPT_COMMS, 0x04);
+ checkValue(KeyFlags.ENCRYPT_STORAGE, 0x08);
+ checkValue(KeyFlags.SPLIT, 0x10);
+ checkValue(KeyFlags.AUTHENTICATION, 0x20);
+ checkValue(KeyFlags.SHARED, 0x80);
+
+ // yes this actually happens
+ checkValue(new byte[] { 4, 0, 0, 0 }, 0x04);
+ checkValue(new byte[] { 4, 0, 0 }, 0x04);
+ checkValue(new byte[] { 4, 0 }, 0x04);
+ checkValue(new byte[] { 4 }, 0x04);
+ }
+
+ private void checkValue(int flag, int value)
+ {
+ KeyFlags f = new KeyFlags(true, flag);
+
+ if (f.getFlags() != value)
+ {
+ fail("flag value mismatch");
+ }
+ }
+
+ private void checkValue(byte[] flag, int value)
+ {
+ KeyFlags f = new KeyFlags(true, flag);
+
+ if (f.getFlags() != value)
+ {
+ fail("flag value mismatch");
+ }
+ }
+
+ private void testMissingSubpackets(byte[] signature)
+ throws IOException
+ {
+ PGPObjectFactory f = new PGPObjectFactory(signature);
+ Object obj = f.nextObject();
+
+ while (!(obj instanceof PGPSignatureList))
+ {
+ obj = f.nextObject();
+ if (obj instanceof PGPLiteralData)
+ {
+ InputStream in = ((PGPLiteralData)obj).getDataStream();
+ Streams.drain(in);
+ }
+ }
+
+ PGPSignature sig = ((PGPSignatureList)obj).get(0);
+
+ if (sig.getVersion() > 3)
+ {
+ PGPSignatureSubpacketVector v = sig.getHashedSubPackets();
+
+ if (v.getKeyExpirationTime() != 0)
+ {
+ fail("key expiration time not zero for missing subpackets");
+ }
+
+ if (!sig.hasSubpackets())
+ {
+ fail("hasSubpackets() returns false with packets");
+ }
+ }
+ else
+ {
+ if (sig.getHashedSubPackets() != null)
+ {
+ fail("hashed sub packets found when none expected");
+ }
+ if (sig.getUnhashedSubPackets() != null)
+ {
+ fail("unhashed sub packets found when none expected");
+ }
+
+ if (sig.hasSubpackets())
+ {
+ fail("hasSubpackets() returns true with no packets");
+ }
+ }
+ }
+
+ private void preferredAlgorithmCheck(
+ String type,
+ int[] expected,
+ int[] prefAlgs)
+ {
+ if (expected == null)
+ {
+ if (prefAlgs != null)
+ {
+ fail("preferences for " + type + " found when none expected");
+ }
+ }
+ else
+ {
+ if (prefAlgs.length != expected.length)
+ {
+ fail("wrong number of preferred " + type + " algorithms found");
+ }
+
+ for (int i = 0; i != expected.length; i++)
+ {
+ if (expected[i] != prefAlgs[i])
+ {
+ fail("wrong algorithm found for " + type + ": expected " + expected[i] + " got " + prefAlgs);
+ }
+ }
+ }
+ }
+
+ private void testSig(
+ int encAlgorithm,
+ int hashAlgorithm,
+ PGPPublicKey pubKey,
+ PGPPrivateKey privKey)
+ throws Exception
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ ByteArrayInputStream testIn = new ByteArrayInputStream(TEST_DATA);
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(encAlgorithm, hashAlgorithm, "BC");
+
+ sGen.initSign(PGPSignature.BINARY_DOCUMENT, privKey);
+ sGen.generateOnePassVersion(false).encode(bOut);
+
+ PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
+ OutputStream lOut = lGen.open(
+ new UncloseableOutputStream(bOut),
+ PGPLiteralData.BINARY,
+ "_CONSOLE",
+ TEST_DATA.length * 2,
+ new Date());
+
+ int ch;
+ while ((ch = testIn.read()) >= 0)
+ {
+ lOut.write(ch);
+ sGen.update((byte)ch);
+ }
+
+ lOut.write(TEST_DATA);
+ sGen.update(TEST_DATA);
+
+ lGen.close();
+
+ sGen.generate().encode(bOut);
+
+ verifySignature(bOut.toByteArray(), hashAlgorithm, pubKey, TEST_DATA);
+ }
+
+ private void testTextSig(
+ int encAlgorithm,
+ int hashAlgorithm,
+ PGPPublicKey pubKey,
+ PGPPrivateKey privKey,
+ byte[] data,
+ byte[] canonicalData)
+ throws Exception
+ {
+ PGPSignatureGenerator sGen = new PGPSignatureGenerator(encAlgorithm, HashAlgorithmTags.SHA1, "BC");
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ ByteArrayInputStream testIn = new ByteArrayInputStream(data);
+ Date creationTime = new Date();
+
+ sGen.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, privKey);
+ sGen.generateOnePassVersion(false).encode(bOut);
+
+ PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
+ OutputStream lOut = lGen.open(
+ new UncloseableOutputStream(bOut),
+ PGPLiteralData.TEXT,
+ "_CONSOLE",
+ data.length * 2,
+ creationTime);
+
+ int ch;
+ while ((ch = testIn.read()) >= 0)
+ {
+ lOut.write(ch);
+ sGen.update((byte)ch);
+ }
+
+ lOut.write(data);
+ sGen.update(data);
+
+ lGen.close();
+
+ PGPSignature sig = sGen.generate();
+
+ if (sig.getCreationTime().getTime() == 0)
+ {
+ fail("creation time not set in v4 signature");
+ }
+
+ sig.encode(bOut);
+
+ verifySignature(bOut.toByteArray(), hashAlgorithm, pubKey, canonicalData);
+ }
+
+ private void testSigV3(
+ int encAlgorithm,
+ int hashAlgorithm,
+ PGPPublicKey pubKey,
+ PGPPrivateKey privKey)
+ throws Exception
+ {
+ byte[] bytes = generateV3BinarySig(privKey, encAlgorithm, hashAlgorithm);
+
+ verifySignature(bytes, hashAlgorithm, pubKey, TEST_DATA);
+ }
+
+ private byte[] generateV3BinarySig(PGPPrivateKey privKey, int encAlgorithm, int hashAlgorithm)
+ throws Exception
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ ByteArrayInputStream testIn = new ByteArrayInputStream(TEST_DATA);
+ PGPV3SignatureGenerator sGen = new PGPV3SignatureGenerator(encAlgorithm, hashAlgorithm, "BC");
+
+ sGen.initSign(PGPSignature.BINARY_DOCUMENT, privKey);
+ sGen.generateOnePassVersion(false).encode(bOut);
+
+ PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
+ OutputStream lOut = lGen.open(
+ new UncloseableOutputStream(bOut),
+ PGPLiteralData.BINARY,
+ "_CONSOLE",
+ TEST_DATA.length * 2,
+ new Date());
+
+ int ch;
+ while ((ch = testIn.read()) >= 0)
+ {
+ lOut.write(ch);
+ sGen.update((byte)ch);
+ }
+
+ lOut.write(TEST_DATA);
+ sGen.update(TEST_DATA);
+
+ lGen.close();
+
+ sGen.generate().encode(bOut);
+
+ return bOut.toByteArray();
+ }
+
+ private void testTextSigV3(
+ int encAlgorithm,
+ int hashAlgorithm,
+ PGPPublicKey pubKey,
+ PGPPrivateKey privKey,
+ byte[] data,
+ byte[] canonicalData)
+ throws Exception
+ {
+ PGPV3SignatureGenerator sGen = new PGPV3SignatureGenerator(encAlgorithm, HashAlgorithmTags.SHA1, "BC");
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ ByteArrayInputStream testIn = new ByteArrayInputStream(data);
+
+ sGen.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, privKey);
+ sGen.generateOnePassVersion(false).encode(bOut);
+
+ PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
+ OutputStream lOut = lGen.open(
+ new UncloseableOutputStream(bOut),
+ PGPLiteralData.TEXT,
+ "_CONSOLE",
+ data.length * 2,
+ new Date());
+
+ int ch;
+ while ((ch = testIn.read()) >= 0)
+ {
+ lOut.write(ch);
+ sGen.update((byte)ch);
+ }
+
+ lOut.write(data);
+ sGen.update(data);
+
+ lGen.close();
+
+ PGPSignature sig = sGen.generate();
+
+ if (sig.getCreationTime().getTime() == 0)
+ {
+ fail("creation time not set in v3 signature");
+ }
+
+ sig.encode(bOut);
+
+ verifySignature(bOut.toByteArray(), hashAlgorithm, pubKey, canonicalData);
+ }
+
+ private void verifySignature(
+ byte[] encodedSig,
+ int hashAlgorithm,
+ PGPPublicKey pubKey,
+ byte[] original)
+ throws IOException, PGPException, NoSuchProviderException, SignatureException
+ {
+ PGPObjectFactory pgpFact = new PGPObjectFactory(encodedSig);
+ PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
+ PGPOnePassSignature ops = p1.get(0);
+ PGPLiteralData p2 = (PGPLiteralData)pgpFact.nextObject();
+ InputStream dIn = p2.getInputStream();
+
+ ops.initVerify(pubKey, "BC");
+
+ int ch;
+
+ while ((ch = dIn.read()) >= 0)
+ {
+ ops.update((byte)ch);
+ }
+
+ PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
+ PGPSignature sig = p3.get(0);
+
+ Date creationTime = sig.getCreationTime();
+ Date now = new Date();
+
+ // Check creationTime is recent
+ if (creationTime.after(now)
+ || creationTime.before(new Date(now.getTime() - 10 * 60 * 1000)))
+ {
+ fail("bad creation time in signature: " + creationTime);
+ }
+
+ if (sig.getKeyID() != pubKey.getKeyID())
+ {
+ fail("key id mismatch in signature");
+ }
+
+ if (!ops.verify(sig))
+ {
+ fail("Failed generated signature check - " + hashAlgorithm);
+ }
+
+ sig.initVerify(pubKey, "BC");
+
+ for (int i = 0; i != original.length; i++)
+ {
+ sig.update(original[i]);
+ }
+
+ sig.update(original);
+
+ if (!sig.verify())
+ {
+ fail("Failed generated signature check against original data");
+ }
+ }
+
+ public String getName()
+ {
+ return "PGPSignatureTest";
+ }
+
+ public static void main(
+ String[] args)
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ runTest(new PGPSignatureTest());
+ }
+}
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPUnicodeTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPUnicodeTest.java
new file mode 100644
index 00000000..337f8ff3
--- /dev/null
+++ b/pg/src/test/java/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/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java
new file mode 100644
index 00000000..2810cb96
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java
@@ -0,0 +1,46 @@
+package org.bouncycastle.openpgp.test;
+
+import java.security.Security;
+
+import org.bouncycastle.util.test.Test;
+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 PGPNoPrivateKeyTest()
+ };
+
+ public static void main(
+ String[] args)
+ {
+ Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
+
+ for (int i = 0; i != tests.length; i++)
+ {
+ TestResult result = tests[i].perform();
+ System.out.println(result);
+ if (result.getException() != null)
+ {
+ result.getException().printStackTrace();
+ }
+ }
+ }
+}
+