From 2b976f5364cfdbc37d3086019d93483c983eb80b Mon Sep 17 00:00:00 2001 From: David Hook Date: Fri, 31 May 2013 17:07:45 +1000 Subject: initial reshuffle --- .../bouncycastle/apache/bzip2/BZip2Constants.java | 100 + .../apache/bzip2/CBZip2InputStream.java | 848 +++++++ .../apache/bzip2/CBZip2OutputStream.java | 1651 +++++++++++++ .../java/org/bouncycastle/apache/bzip2/CRC.java | 131 + .../org/bouncycastle/bcpg/ArmoredInputStream.java | 473 ++++ .../org/bouncycastle/bcpg/ArmoredOutputStream.java | 411 +++ .../org/bouncycastle/bcpg/BCPGInputStream.java | 391 +++ .../main/java/org/bouncycastle/bcpg/BCPGKey.java | 24 + .../java/org/bouncycastle/bcpg/BCPGObject.java | 24 + .../org/bouncycastle/bcpg/BCPGOutputStream.java | 361 +++ pg/src/main/java/org/bouncycastle/bcpg/CRC24.java | 37 + .../bouncycastle/bcpg/CompressedDataPacket.java | 31 + .../bcpg/CompressionAlgorithmTags.java | 12 + .../org/bouncycastle/bcpg/ContainedPacket.java | 26 + .../org/bouncycastle/bcpg/DSAPublicBCPGKey.java | 116 + .../org/bouncycastle/bcpg/DSASecretBCPGKey.java | 82 + .../bouncycastle/bcpg/ElGamalPublicBCPGKey.java | 93 + .../bouncycastle/bcpg/ElGamalSecretBCPGKey.java | 79 + .../org/bouncycastle/bcpg/ExperimentalPacket.java | 46 + .../org/bouncycastle/bcpg/HashAlgorithmTags.java | 20 + .../org/bouncycastle/bcpg/InputStreamPacket.java | 26 + .../org/bouncycastle/bcpg/LiteralDataPacket.java | 74 + .../main/java/org/bouncycastle/bcpg/MPInteger.java | 62 + .../java/org/bouncycastle/bcpg/MarkerPacket.java | 28 + .../bouncycastle/bcpg/ModDetectionCodePacket.java | 45 + .../bouncycastle/bcpg/OnePassSignaturePacket.java | 115 + .../org/bouncycastle/bcpg/OutputStreamPacket.java | 18 + pg/src/main/java/org/bouncycastle/bcpg/Packet.java | 9 + .../java/org/bouncycastle/bcpg/PacketTags.java | 31 + .../bouncycastle/bcpg/PublicKeyAlgorithmTags.java | 29 + .../bcpg/PublicKeyEncSessionPacket.java | 112 + .../org/bouncycastle/bcpg/PublicKeyPacket.java | 126 + .../org/bouncycastle/bcpg/PublicSubkeyPacket.java | 40 + .../org/bouncycastle/bcpg/RSAPublicBCPGKey.java | 91 + .../org/bouncycastle/bcpg/RSASecretBCPGKey.java | 176 ++ pg/src/main/java/org/bouncycastle/bcpg/S2K.java | 151 ++ .../org/bouncycastle/bcpg/SecretKeyPacket.java | 185 ++ .../org/bouncycastle/bcpg/SecretSubkeyPacket.java | 58 + .../org/bouncycastle/bcpg/SignaturePacket.java | 515 ++++ .../org/bouncycastle/bcpg/SignatureSubpacket.java | 81 + .../bcpg/SignatureSubpacketInputStream.java | 123 + .../bouncycastle/bcpg/SignatureSubpacketTags.java | 32 + .../bouncycastle/bcpg/SymmetricEncDataPacket.java | 14 + .../bcpg/SymmetricEncIntegrityPacket.java | 20 + .../bcpg/SymmetricKeyAlgorithmTags.java | 19 + .../bcpg/SymmetricKeyEncSessionPacket.java | 90 + .../java/org/bouncycastle/bcpg/TrustPacket.java | 48 + .../org/bouncycastle/bcpg/UserAttributePacket.java | 60 + .../bouncycastle/bcpg/UserAttributeSubpacket.java | 91 + .../bcpg/UserAttributeSubpacketInputStream.java | 116 + .../bcpg/UserAttributeSubpacketTags.java | 9 + .../java/org/bouncycastle/bcpg/UserIDPacket.java | 39 + .../org/bouncycastle/bcpg/attr/ImageAttribute.java | 77 + .../bouncycastle/bcpg/sig/EmbeddedSignature.java | 18 + .../java/org/bouncycastle/bcpg/sig/Exportable.java | 46 + .../java/org/bouncycastle/bcpg/sig/Features.java | 98 + .../org/bouncycastle/bcpg/sig/IssuerKeyID.java | 50 + .../bouncycastle/bcpg/sig/KeyExpirationTime.java | 50 + .../java/org/bouncycastle/bcpg/sig/KeyFlags.java | 73 + .../org/bouncycastle/bcpg/sig/NotationData.java | 104 + .../bouncycastle/bcpg/sig/PreferredAlgorithms.java | 59 + .../org/bouncycastle/bcpg/sig/PrimaryUserID.java | 46 + .../java/org/bouncycastle/bcpg/sig/Revocable.java | 46 + .../org/bouncycastle/bcpg/sig/RevocationKey.java | 52 + .../bouncycastle/bcpg/sig/RevocationKeyTags.java | 8 + .../bouncycastle/bcpg/sig/RevocationReason.java | 51 + .../bcpg/sig/RevocationReasonTags.java | 12 + .../bcpg/sig/SignatureCreationTime.java | 48 + .../bcpg/sig/SignatureExpirationTime.java | 48 + .../org/bouncycastle/bcpg/sig/SignerUserID.java | 50 + .../org/bouncycastle/bcpg/sig/TrustSignature.java | 48 + .../bouncycastle/openpgp/PGPCompressedData.java | 143 ++ .../openpgp/PGPCompressedDataGenerator.java | 201 ++ .../openpgp/PGPDataValidationException.java | 17 + .../org/bouncycastle/openpgp/PGPEncryptedData.java | 147 ++ .../openpgp/PGPEncryptedDataGenerator.java | 537 ++++ .../bouncycastle/openpgp/PGPEncryptedDataList.java | 75 + .../org/bouncycastle/openpgp/PGPException.java | 35 + .../java/org/bouncycastle/openpgp/PGPKeyFlags.java | 19 + .../java/org/bouncycastle/openpgp/PGPKeyPair.java | 148 ++ .../java/org/bouncycastle/openpgp/PGPKeyRing.java | 125 + .../bouncycastle/openpgp/PGPKeyRingGenerator.java | 272 ++ .../openpgp/PGPKeyValidationException.java | 16 + .../org/bouncycastle/openpgp/PGPLiteralData.java | 96 + .../openpgp/PGPLiteralDataGenerator.java | 202 ++ .../java/org/bouncycastle/openpgp/PGPMarker.java | 34 + .../org/bouncycastle/openpgp/PGPObjectFactory.java | 151 ++ .../bouncycastle/openpgp/PGPOnePassSignature.java | 266 ++ .../openpgp/PGPOnePassSignatureList.java | 40 + .../bouncycastle/openpgp/PGPPBEEncryptedData.java | 180 ++ .../org/bouncycastle/openpgp/PGPPrivateKey.java | 138 ++ .../org/bouncycastle/openpgp/PGPPublicKey.java | 964 ++++++++ .../openpgp/PGPPublicKeyEncryptedData.java | 262 ++ .../org/bouncycastle/openpgp/PGPPublicKeyRing.java | 273 ++ .../openpgp/PGPPublicKeyRingCollection.java | 369 +++ .../org/bouncycastle/openpgp/PGPSecretKey.java | 937 +++++++ .../org/bouncycastle/openpgp/PGPSecretKeyRing.java | 480 ++++ .../openpgp/PGPSecretKeyRingCollection.java | 367 +++ .../org/bouncycastle/openpgp/PGPSignature.java | 564 +++++ .../openpgp/PGPSignatureGenerator.java | 574 +++++ .../org/bouncycastle/openpgp/PGPSignatureList.java | 40 + .../openpgp/PGPSignatureSubpacketGenerator.java | 197 ++ .../openpgp/PGPSignatureSubpacketVector.java | 277 +++ .../openpgp/PGPUserAttributeSubpacketVector.java | 93 + .../PGPUserAttributeSubpacketVectorGenerator.java | 27 + .../java/org/bouncycastle/openpgp/PGPUtil.java | 376 +++ .../openpgp/PGPV3SignatureGenerator.java | 286 +++ .../org/bouncycastle/openpgp/StreamGenerator.java | 9 + .../openpgp/WrappedGeneratorStream.java | 46 + .../openpgp/examples/ByteArrayHandler.java | 206 ++ .../openpgp/examples/ClearSignedFileProcessor.java | 390 +++ .../examples/DSAElGamalKeyRingGenerator.java | 139 ++ .../examples/DetachedSignatureProcessor.java | 198 ++ .../openpgp/examples/DirectKeySignature.java | 135 + .../openpgp/examples/KeyBasedFileProcessor.java | 279 +++ .../examples/KeyBasedLargeFileProcessor.java | 283 +++ .../openpgp/examples/PBEFileProcessor.java | 214 ++ .../openpgp/examples/PGPExampleUtil.java | 153 ++ .../bouncycastle/openpgp/examples/PubringDump.java | 102 + .../openpgp/examples/RSAKeyPairGenerator.java | 114 + .../openpgp/examples/SignedFileProcessor.java | 215 ++ .../openpgp/operator/KeyFingerPrintCalculator.java | 10 + .../openpgp/operator/PBEDataDecryptorFactory.java | 26 + .../operator/PBEKeyEncryptionMethodGenerator.java | 91 + .../openpgp/operator/PBESecretKeyDecryptor.java | 31 + .../openpgp/operator/PBESecretKeyEncryptor.java | 104 + .../openpgp/operator/PGPContentSigner.java | 20 + .../openpgp/operator/PGPContentSignerBuilder.java | 10 + .../openpgp/operator/PGPContentVerifier.java | 20 + .../operator/PGPContentVerifierBuilder.java | 10 + .../PGPContentVerifierBuilderProvider.java | 9 + .../openpgp/operator/PGPDataDecryptor.java | 12 + .../openpgp/operator/PGPDataDecryptorFactory.java | 9 + .../openpgp/operator/PGPDataDecryptorProvider.java | 5 + .../openpgp/operator/PGPDataEncryptor.java | 12 + .../openpgp/operator/PGPDataEncryptorBuilder.java | 15 + .../openpgp/operator/PGPDigestCalculator.java | 35 + .../operator/PGPDigestCalculatorProvider.java | 9 + .../operator/PGPKeyEncryptionMethodGenerator.java | 10 + .../org/bouncycastle/openpgp/operator/PGPUtil.java | 216 ++ .../operator/PublicKeyDataDecryptorFactory.java | 12 + .../PublicKeyKeyEncryptionMethodGenerator.java | 78 + .../openpgp/operator/bc/BcImplProvider.java | 138 ++ .../operator/bc/BcKeyFingerprintCalculator.java | 68 + .../operator/bc/BcPBEDataDecryptorFactory.java | 67 + .../bc/BcPBEKeyEncryptionMethodGenerator.java | 97 + .../bc/BcPBESecretKeyDecryptorBuilder.java | 43 + .../bc/BcPBESecretKeyEncryptorBuilder.java | 142 ++ .../operator/bc/BcPGPContentSignerBuilder.java | 98 + .../bc/BcPGPContentVerifierBuilderProvider.java | 75 + .../operator/bc/BcPGPDataEncryptorBuilder.java | 118 + .../operator/bc/BcPGPDigestCalculatorProvider.java | 82 + .../openpgp/operator/bc/BcPGPKeyConverter.java | 183 ++ .../openpgp/operator/bc/BcPGPKeyPair.java | 33 + .../bc/BcPublicKeyDataDecryptorFactory.java | 109 + .../BcPublicKeyKeyEncryptionMethodGenerator.java | 68 + .../bouncycastle/openpgp/operator/bc/BcUtil.java | 75 + .../operator/bc/SHA1PGPDigestCalculator.java | 68 + .../openpgp/operator/bc/SignerOutputStream.java | 35 + .../jcajce/JcaKeyFingerprintCalculator.java | 72 + .../jcajce/JcaPGPContentSignerBuilder.java | 156 ++ .../JcaPGPContentVerifierBuilderProvider.java | 112 + .../JcaPGPDigestCalculatorProviderBuilder.java | 119 + .../operator/jcajce/JcaPGPKeyConverter.java | 258 ++ .../openpgp/operator/jcajce/JcaPGPKeyPair.java | 34 + .../openpgp/operator/jcajce/JcaPGPPrivateKey.java | 34 + .../jcajce/JcePBEDataDecryptorFactoryBuilder.java | 99 + .../jcajce/JcePBEKeyEncryptionMethodGenerator.java | 132 + .../jcajce/JcePBESecretKeyDecryptorBuilder.java | 100 + .../jcajce/JcePBESecretKeyEncryptorBuilder.java | 180 ++ .../jcajce/JcePGPDataEncryptorBuilder.java | 146 ++ .../JcePublicKeyDataDecryptorFactoryBuilder.java | 185 ++ .../JcePublicKeyKeyEncryptionMethodGenerator.java | 93 + .../openpgp/operator/jcajce/OperatorHelper.java | 171 ++ .../openpgp/operator/jcajce/PGPUtil.java | 149 ++ .../operator/jcajce/SHA1PGPDigestCalculator.java | 81 + .../operator/jcajce/SignatureOutputStream.java | 56 + .../openpgp/examples/test/AllTests.java | 415 ++++ .../org/bouncycastle/openpgp/test/AllTests.java | 46 + .../openpgp/test/BcPGPDSAElGamalTest.java | 564 +++++ .../bouncycastle/openpgp/test/BcPGPDSATest.java | 633 +++++ .../openpgp/test/BcPGPKeyRingTest.java | 2362 ++++++++++++++++++ .../bouncycastle/openpgp/test/BcPGPPBETest.java | 400 +++ .../bouncycastle/openpgp/test/BcPGPRSATest.java | 1376 +++++++++++ .../org/bouncycastle/openpgp/test/DSA2Test.java | 290 +++ .../bouncycastle/openpgp/test/PGPArmoredTest.java | 255 ++ .../openpgp/test/PGPClearSignedSignatureTest.java | 454 ++++ .../openpgp/test/PGPCompressionTest.java | 143 ++ .../openpgp/test/PGPDSAElGamalTest.java | 552 +++++ .../org/bouncycastle/openpgp/test/PGPDSATest.java | 628 +++++ .../bouncycastle/openpgp/test/PGPKeyRingTest.java | 2610 ++++++++++++++++++++ .../bouncycastle/openpgp/test/PGPMarkerTest.java | 105 + .../openpgp/test/PGPNoPrivateKeyTest.java | 167 ++ .../org/bouncycastle/openpgp/test/PGPPBETest.java | 396 +++ .../bouncycastle/openpgp/test/PGPPacketTest.java | 103 + .../org/bouncycastle/openpgp/test/PGPRSATest.java | 1471 +++++++++++ .../openpgp/test/PGPSignatureTest.java | 780 ++++++ .../bouncycastle/openpgp/test/PGPUnicodeTest.java | 183 ++ .../bouncycastle/openpgp/test/RegressionTest.java | 46 + 199 files changed, 38383 insertions(+) create mode 100644 pg/src/main/java/org/bouncycastle/apache/bzip2/BZip2Constants.java create mode 100644 pg/src/main/java/org/bouncycastle/apache/bzip2/CBZip2InputStream.java create mode 100644 pg/src/main/java/org/bouncycastle/apache/bzip2/CBZip2OutputStream.java create mode 100644 pg/src/main/java/org/bouncycastle/apache/bzip2/CRC.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/ArmoredInputStream.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/ArmoredOutputStream.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/BCPGInputStream.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/BCPGKey.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/BCPGObject.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/BCPGOutputStream.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/CRC24.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/CompressedDataPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/CompressionAlgorithmTags.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/ContainedPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/DSAPublicBCPGKey.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/DSASecretBCPGKey.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/ElGamalPublicBCPGKey.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/ElGamalSecretBCPGKey.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/ExperimentalPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/HashAlgorithmTags.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/InputStreamPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/LiteralDataPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/MPInteger.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/MarkerPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/ModDetectionCodePacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/OnePassSignaturePacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/OutputStreamPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/Packet.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/PacketTags.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/PublicKeyAlgorithmTags.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/PublicKeyEncSessionPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/PublicKeyPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/PublicSubkeyPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/RSAPublicBCPGKey.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/RSASecretBCPGKey.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/S2K.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/SecretSubkeyPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/SignaturePacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/SignatureSubpacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/SignatureSubpacketInputStream.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/SignatureSubpacketTags.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/SymmetricEncDataPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/SymmetricEncIntegrityPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyAlgorithmTags.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/TrustPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/UserAttributePacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/UserAttributeSubpacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/UserAttributeSubpacketInputStream.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/UserAttributeSubpacketTags.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/UserIDPacket.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/attr/ImageAttribute.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/EmbeddedSignature.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/Exportable.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/Features.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/IssuerKeyID.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/KeyExpirationTime.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/KeyFlags.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/NotationData.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/PreferredAlgorithms.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/PrimaryUserID.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/Revocable.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationKey.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationKeyTags.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationReason.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/RevocationReasonTags.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/SignatureCreationTime.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/SignatureExpirationTime.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/SignerUserID.java create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/sig/TrustSignature.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPCompressedData.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPCompressedDataGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPDataValidationException.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedData.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedDataList.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPException.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPKeyFlags.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRing.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPKeyValidationException.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPLiteralData.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPLiteralDataGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPMarker.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPObjectFactory.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignature.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignatureList.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPPBEEncryptedData.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPPrivateKey.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyRing.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyRingCollection.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRingCollection.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureList.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPUserAttributeSubpacketVector.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPUserAttributeSubpacketVectorGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPUtil.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/StreamGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/WrappedGeneratorStream.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/examples/ByteArrayHandler.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/examples/ClearSignedFileProcessor.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/examples/DetachedSignatureProcessor.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/examples/DirectKeySignature.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/examples/KeyBasedFileProcessor.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/examples/KeyBasedLargeFileProcessor.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/examples/PBEFileProcessor.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/examples/PGPExampleUtil.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/examples/PubringDump.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/examples/RSAKeyPairGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/examples/SignedFileProcessor.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/KeyFingerPrintCalculator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PBEDataDecryptorFactory.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyDecryptor.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptor.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSigner.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSignerBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentVerifier.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentVerifierBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataDecryptor.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataDecryptorFactory.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataDecryptorProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataEncryptor.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDataEncryptorBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDigestCalculator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPDigestCalculatorProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPUtil.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcImplProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyConverter.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPair.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcUtil.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/SignerOutputStream.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/OperatorHelper.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/PGPUtil.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/SignatureOutputStream.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/examples/test/AllTests.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/AllTests.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPDSAElGamalTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPDSATest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPKeyRingTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPPBETest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/BcPGPRSATest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/DSA2Test.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPArmoredTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPClearSignedSignatureTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPCompressionTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPDSAElGamalTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPDSATest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyRingTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPMarkerTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPNoPrivateKeyTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPPBETest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPPacketTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPRSATest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPSignatureTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPUnicodeTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java (limited to 'pg/src') 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 + * 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 Keiron Liddle + */ +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 + * 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 Keiron Liddle + * + * NB: 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 + * 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 Keiron Liddle + * + * TODO: Update to BZip2 1.0.1 + * NB: 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 + * 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 Keiron Liddle + */ +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 + { + } + + /** + * Note: 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. + *

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

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

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

+ *

+ * Note: using this may break compatibility with RFC 1991 compliant tools. Only recent OpenPGP + * implementations are capable of accepting these streams. + *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ * To sign a file: ClearSignedFileProcessor -s fileName secretKey passPhrase.
+ *

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

+ * usage: DSAElGamalKeyRingGenerator [-a] identity passPhrase + *

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

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

+ * To sign a file: DetachedSignatureProcessor -s [-a] fileName secretKey passPhrase.
+ * If -a is specified the output file will be "ascii-armored". + *

+ * To decrypt: DetachedSignatureProcessor -v fileName signatureFile publicKeyFile. + *

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

+ * To sign a key: DirectKeySignature secretKeyFile secretKeyPass publicKeyFile(key to be signed) NotationName NotationValue.
+ *

+ * To display a NotationData packet from a publicKey previously signed: DirectKeySignature signedPublicKeyFile.
+ *

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

+ * To encrypt a file: KeyBasedFileProcessor -e [-a|-ai] fileName publicKeyFile.
+ * If -a is specified the output file will be "ascii-armored". + * If -i is specified the output file will be have integrity checking added. + *

+ * To decrypt: KeyBasedFileProcessor -d fileName secretKeyFile passPhrase. + *

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

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

+ * To encrypt a file: KeyBasedLargeFileProcessor -e [-a|-ai] fileName publicKeyFile.
+ * If -a is specified the output file will be "ascii-armored". + * If -i is specified the output file will be have integrity checking added. + *

+ * To decrypt: KeyBasedLargeFileProcessor -d fileName secretKeyFile passPhrase. + *

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

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

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

+ * To encrypt a file: PBEFileProcessor -e [-ai] fileName passPhrase.
+ * If -a is specified the output file will be "ascii-armored".
+ * If -i is specified the output file will be "integrity protected". + *

+ * To decrypt: PBEFileProcessor -d fileName passPhrase. + *

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

+ * usage: RSAKeyPairGenerator [-a] identity passPhrase + *

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

+ * To sign a file: SignedFileProcessor -s [-a] fileName secretKey passPhrase.
+ * If -a is specified the output file will be "ascii-armored". + *

+ * To decrypt: SignedFileProcessor -v fileName publicKeyFile. + *

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

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

+ * This method is only used for processing version 3 keys. + *

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

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

+ * 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( + "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"); + + char[] sec5pass1 = "12345678".toCharArray(); + + // + // Werner Koch "odd keys" + // + byte[] pub6 = Base64.decode( + "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" + + "L4Kb7TWkd/OHQScVBO8sTUz0+2g="); + + byte[] pub6check = Base64.decode("62O9"); + + // + // revoked sub key + // + byte[] pub7 = Base64.decode( + "mQGiBEFOsIwRBADcjRx7nAs4RaWsQU6p8/ECLZD9sSeYc6CN6UDI96RKj0/hCzMs" + + "qlA0+9fzGZ7ZEJ34nuvDKlhKGC7co5eOiE0a9EijxgcrZU/LClZWa4YfyNg/ri6I" + + "yTyfOfrPQ33GNQt2iImDf3FKp7XKuY9nIxicGQEaW0kkuAmbV3oh0+9q8QCg/+fS" + + "epDEqEE/+nKONULGizKUjMED/RtL6RThRftZ9DOSdBytGYd48z35pca/qZ6HA36K" + + "PVQwi7V77VKQyKFLTOXPLnVyO85hyYB/Nv4DFHN+vcC7/49lfoyYMZlN+LarckHi" + + "NL154wmmzygB/KKysvWBLgkErEBCD0xBDd89iTQNlDtVQAWGORVffl6WWjOAkliG" + + "3dL6A/9A288HfFRnywqi3xddriV6wCPmStC3dkCS4vHk2ofS8uw4ZNoRlp1iEPna" + + "ai2Xa9DX1tkhaGk2k96MqqbBdGpbW8sMA9otJ9xdMjWEm/CgJUFUFQf3zaVy3mkM" + + "S2Lvb6P4Wc2l/diEEIyK8+PqJItSh0OVU3K9oM7ngHwVcalKILQVUkV2b2tlZCA8" + + "UmV2b2tlZEB0ZWQ+iQBOBBARAgAOBQJBTrCMBAsDAgECGQEACgkQvglkcFA/c63+" + + "QgCguh8rsJbPTtbhZcrqBi5Mo1bntLEAoPZQ0Kjmu2knRUpHBeUemHDB6zQeuQIN" + + "BEFOsIwQCAD2Qle3CH8IF3KiutapQvMF6PlTETlPtvFuuUs4INoBp1ajFOmPQFXz" + + "0AfGy0OplK33TGSGSfgMg71l6RfUodNQ+PVZX9x2Uk89PY3bzpnhV5JZzf24rnRP" + + "xfx2vIPFRzBhznzJZv8V+bv9kV7HAarTW56NoKVyOtQa8L9GAFgr5fSI/VhOSdvN" + + "ILSd5JEHNmszbDgNRR0PfIizHHxbLY7288kjwEPwpVsYjY67VYy4XTjTNP18F1dD" + + "ox0YbN4zISy1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6ypUM2Zafq9AKUJsCRtMI" + + "PWakXUGfnHy9iUsiGSa6q6Jew1XpMgs7AAICB/93zriSvSHqsi1FeEmUBo431Jkh" + + "VerIzb6Plb1j6FIq+s3vyvx9K+dMvjotZqylWZj4GXpH+2xLJTjWkrGSfUZVI2Nk" + + "nyOFxUCKLLqaqVBFAQIjULfvQfGEWiGQKk9aRLkdG+D+8Y2N9zYoBXoQ9arvvS/t" + + "4mlOsiuaTe+BZ4x+BXTpF4b9sKZl7V8QP/TkoJWUdydkvxciHdWp7ssqyiKOFRhG" + + "818knDfFQ3cn2w/RnOb+7AF9wDncXDPYLfpPv9b2qZoLrXcyvlLffGDUdWs553ut" + + "1F5AprMURs8BGmY9BnjggfVubHdhTUoA4gVvrdaf+D9NwZAl0xK/5Y/oPuMZiQBG" + + "BBgRAgAGBQJBTrCMAAoJEL4JZHBQP3Ot09gAoMmLKloVDP+WhDXnsM5VikxysZ4+" + + "AKCrJAUO+lYAyPYwEwgK+bKmUGeKrIkARgQoEQIABgUCQU6wpQAKCRC+CWRwUD9z" + + "rQK4AJ98kKFxGU6yhHPr6jYBJPWemTNOXgCfeGB3ox4PXeS4DJDuLy9yllytOjo="); + + byte[] pub7check = Base64.decode("f/YQ"); + + byte[] pub8 = Base64.decode( + "mQGiBEEcraYRBADFYj+uFOhHz5SdECvJ3Z03P47gzmWLQ5HH8fPYC9rrv7AgqFFX" + + "aWlJJVMLua9e6xoCiDWJs/n4BbZ/weL/11ELg6XqUnzFhYyz0H2KFsPgQ/b9lWLY" + + "MtcPMFy5jE33hv/ixHgYLFqoNaAIbg0lzYEW/otQ9IhRl16fO1Q/CQZZrQCg/9M2" + + "V2BTmm9RYog86CXJtjawRBcD/RIqU0zulxZ2Zt4javKVxrGIwW3iBU935ebmJEIK" + + "Y5EVkGKBOCvsApZ+RGzpYeR2uMsTnQi8RJgiAnjaoVPCdsVJE7uQ0h8XuJ5n5mJ2" + + "kLCFlF2hj5ViicZzse+crC12CGtgRe8z23ubLRcd6IUGhVutK8/b5knZ22vE14JD" + + "ykKdA/96ObzJQdiuuPsEWN799nUUCaYWPAoLAmiXuICSP4GEnxLbYHWo8zhMrVMT" + + "9Q5x3h8cszUz7Acu2BXjP1m96msUNoxPOZtt88NlaFz1Q/JSbQTsVOMd9b/IRN6S" + + "A/uU0BiKEMHXuT8HUHVPK49oCKhZrGFP3RT8HZxDKLmR/qrgZ7ABh7QhSmlhIFlp" + + "eXUgPHl5amlhQG5vd21lZGlhdGVjaC5jb20+sAMD//+JAF0EEBECAB0FAkEcraYH" + + "CwkIBwMCCgIZAQUbAwAAAAUeAQAAAAAKCRD0/lb4K/9iFJlhAKCRMifQewiX5o8F" + + "U099FG3QnLVUZgCfWpMOsHulGHfNrxdBSkE5Urqh1ymwAWe5Ag0EQRytphAIAPZC" + + "V7cIfwgXcqK61qlC8wXo+VMROU+28W65Szgg2gGnVqMU6Y9AVfPQB8bLQ6mUrfdM" + + "ZIZJ+AyDvWXpF9Sh01D49Vlf3HZSTz09jdvOmeFXklnN/biudE/F/Ha8g8VHMGHO" + + "fMlm/xX5u/2RXscBqtNbno2gpXI61Brwv0YAWCvl9Ij9WE5J280gtJ3kkQc2azNs" + + "OA1FHQ98iLMcfFstjvbzySPAQ/ClWxiNjrtVjLhdONM0/XwXV0OjHRhs3jMhLLUq" + + "/zzhsSlAGBGNfISnCnLWhsQDGcgHKXrKlQzZlp+r0ApQmwJG0wg9ZqRdQZ+cfL2J" + + "SyIZJrqrol7DVekyCzsAAgIH/3K2wKRSzkIpDfZR25+tnQ8brv3TYoDZo3/wN3F/" + + "r6PGjx0150Q8g8EAC0bqm4rXWzOqdSxYxvIPOAGm5P4y+884yS6j3vKcXitT7vj+" + + "ODc2pVwGDLDjrMRrosSK89ycPCK6R/5pD7Rv4l9DWi2fgLvXqJHS2/ujUf2uda9q" + + "i9xNMnBXIietR82Sih4undFUOwh6Mws/o3eed9DIdaqv2Y2Aw43z/rJ6cjSGV3C7" + + "Rkf9x85AajYA3LwpS8d99tgFig2u6V/A16oi6/M51oT0aR/ZAk50qUc4WBk9uRUX" + + "L3Y+P6v6FCBE/06fgVltwcQHO1oKYKhH532tDL+9mW5/dYGwAYeJAEwEGBECAAwF" + + "AkEcraYFGwwAAAAACgkQ9P5W+Cv/YhShrgCg+JW8m5nF3R/oZGuG87bXQBszkjMA" + + "oLhGPncuGKowJXMRVc70/8qwXQJLsAFnmQGiBD2K5rYRBADD6kznWZA9nH/pMlk0" + + "bsG4nI3ELgyI7KpgRSS+Dr17+CCNExxCetT+fRFpiEvUcSxeW4pOe55h0bQWSqLo" + + "MNErXVJEXrm1VPkC08W8D/gZuPIsdtKJu4nowvdoA+WrI473pbeONGjaEDbuIJak" + + "yeKM1VMSGhsImdKtxqhndq2/6QCg/xARUIzPRvKr2TJ52K393895X1kEAMCdjSs+" + + "vABnhaeNNR5+NNkkIOCCjCS8qZRZ4ZnIayvn9ueG3KrhZeBIHoajUHrlTXBVj7XO" + + "wXVfGpW17jCDiqhU8Pu6VwEwX1iFbuUwqBffiRLXKg0zfcN+MyFKToi+VsJi4jiZ" + + "zcwUFMb8jE8tvR/muXti7zKPRPCbNBExoCt4A/0TgkzAosG/W4dUkkbc6XoHrjob" + + "iYuy6Xbs/JYlV0vf2CyuKCZC6UoznO5x2GkvOyVtAgyG4HSh1WybdrutZ8k0ysks" + + "mOthE7n7iczdj9Uwg2h+TfgDUnxcCAwxnOsX5UaBqGdkX1PjCWs+O3ZhUDg6UsZc" + + "7O5a3kstf16lHpf4q7ABAIkAYQQfEQIAIQUCPYrmtgIHABcMgBHRi/xlIgI+Q6LT" + + "kNJ7zKvTd87NHAAKCRDJM3gHb/sRj7bxAJ9f6mdlXQH7gMaYiY5tBe/FRtPr1gCf" + + "UhDJQG0ARvORFWHjwhhBMLxW7j2wAWC0KkRlc21vbmQgS2VlIDxkZXNtb25kLmtl" + + "ZUBub3dtZWRpYXRlY2guY29tPrADAQD9iQBYBBARAgAYBQI9iua2CAsDCQgHAgEK" + + "AhkBBRsDAAAAAAoJEMkzeAdv+xGP7v4An19iqadBCCgDIe2DTpspOMidwQYPAJ4/" + + "5QXbcn4ClhOKTO3ZEZefQvvL27ABYLkCDQQ9iua2EAgA9kJXtwh/CBdyorrWqULz" + + "Bej5UxE5T7bxbrlLOCDaAadWoxTpj0BV89AHxstDqZSt90xkhkn4DIO9ZekX1KHT" + + "UPj1WV/cdlJPPT2N286Z4VeSWc39uK50T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq" + + "01uejaClcjrUGvC/RgBYK+X0iP1YTknbzSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O" + + "9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdXQ6MdGGzeMyEstSr/POGxKUAYEY18hKcK" + + "ctaGxAMZyAcpesqVDNmWn6vQClCbAkbTCD1mpF1Bn5x8vYlLIhkmuquiXsNV6TIL" + + "OwACAgf/SO+bbg+owbFKVN5HgOjOElQZVnCsegwCLqTeQzPPzsWmkGX2qZJPDIRN" + + "RZfJzti6+oLJwaRA/3krjviUty4VKhZ3lKg8fd9U0jEdnw+ePA7yJ6gZmBHL15U5" + + "OKH4Zo+OVgDhO0c+oetFpend+eKcvtoUcRoQoi8VqzYUNG0b/nmZGDlxQe1/ZNbP" + + "HpNf1BAtJXivCEKMD6PVzsLPg2L4tFIvD9faeeuKYQ4jcWtTkBLuIaZba3i3a4wG" + + "xTN20j9HpISVuLW/EfZAK1ef4DNjLmHEU9dMzDqfi+hPmMbGlFqcKr+VjcYIDuje" + + "o+92xm/EWAmlti88r2hZ3MySamHDrLABAIkATAQYEQIADAUCPYrmtgUbDAAAAAAK" + + "CRDJM3gHb/sRjzVTAKDVS+OJLMeS9VLAmT8atVCB42MwIQCgoh1j3ccWnhc/h6B7" + + "9Uqz3fUvGoewAWA="); + + byte[] sec8 = Base64.decode( + "lQHpBEEcraYRBADFYj+uFOhHz5SdECvJ3Z03P47gzmWLQ5HH8fPYC9rrv7AgqFFX" + + "aWlJJVMLua9e6xoCiDWJs/n4BbZ/weL/11ELg6XqUnzFhYyz0H2KFsPgQ/b9lWLY" + + "MtcPMFy5jE33hv/ixHgYLFqoNaAIbg0lzYEW/otQ9IhRl16fO1Q/CQZZrQCg/9M2" + + "V2BTmm9RYog86CXJtjawRBcD/RIqU0zulxZ2Zt4javKVxrGIwW3iBU935ebmJEIK" + + "Y5EVkGKBOCvsApZ+RGzpYeR2uMsTnQi8RJgiAnjaoVPCdsVJE7uQ0h8XuJ5n5mJ2" + + "kLCFlF2hj5ViicZzse+crC12CGtgRe8z23ubLRcd6IUGhVutK8/b5knZ22vE14JD" + + "ykKdA/96ObzJQdiuuPsEWN799nUUCaYWPAoLAmiXuICSP4GEnxLbYHWo8zhMrVMT" + + "9Q5x3h8cszUz7Acu2BXjP1m96msUNoxPOZtt88NlaFz1Q/JSbQTsVOMd9b/IRN6S" + + "A/uU0BiKEMHXuT8HUHVPK49oCKhZrGFP3RT8HZxDKLmR/qrgZ/4JAwLXyWhb4pf4" + + "nmCmD0lDwoYvatLiR7UQVM2MamxClIiT0lCPN9C2AYIFgRWAJNS215Tjx7P/dh7e" + + "8sYfh5XEHErT3dMbsAGHtCFKaWEgWWl5dSA8eXlqaWFAbm93bWVkaWF0ZWNoLmNv" + + "bT6wAwP//4kAXQQQEQIAHQUCQRytpgcLCQgHAwIKAhkBBRsDAAAABR4BAAAAAAoJ" + + "EPT+Vvgr/2IUmWEAoJEyJ9B7CJfmjwVTT30UbdCctVRmAJ9akw6we6UYd82vF0FK" + + "QTlSuqHXKbABZ50CawRBHK2mEAgA9kJXtwh/CBdyorrWqULzBej5UxE5T7bxbrlL" + + "OCDaAadWoxTpj0BV89AHxstDqZSt90xkhkn4DIO9ZekX1KHTUPj1WV/cdlJPPT2N" + + "286Z4VeSWc39uK50T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq01uejaClcjrUGvC/" + + "RgBYK+X0iP1YTknbzSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2O" + + "u1WMuF040zT9fBdXQ6MdGGzeMyEstSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqV" + + "DNmWn6vQClCbAkbTCD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwACAgf/crbApFLO" + + "QikN9lHbn62dDxuu/dNigNmjf/A3cX+vo8aPHTXnRDyDwQALRuqbitdbM6p1LFjG" + + "8g84Aabk/jL7zzjJLqPe8pxeK1Pu+P44NzalXAYMsOOsxGuixIrz3Jw8IrpH/mkP" + + "tG/iX0NaLZ+Au9eokdLb+6NR/a51r2qL3E0ycFciJ61HzZKKHi6d0VQ7CHozCz+j" + + "d5530Mh1qq/ZjYDDjfP+snpyNIZXcLtGR/3HzkBqNgDcvClLx3322AWKDa7pX8DX" + + "qiLr8znWhPRpH9kCTnSpRzhYGT25FRcvdj4/q/oUIET/Tp+BWW3BxAc7WgpgqEfn" + + "fa0Mv72Zbn91gf4JAwITijME9IlFBGAwH6YmBtWIlnDiRbsq/Pxozuhbnes831il" + + "KmdpUKXkiIfHY0MqrEWl3Dfn6PMJGTnhgqXMrDxx3uHrq0Jl2swRnAWIIO8gID7j" + + "uPetUqEviPiwAYeJAEwEGBECAAwFAkEcraYFGwwAAAAACgkQ9P5W+Cv/YhShrgCg" + + "+JW8m5nF3R/oZGuG87bXQBszkjMAoLhGPncuGKowJXMRVc70/8qwXQJLsAFn"); + + char[] sec8pass = "qwertyui".toCharArray(); + + byte[] sec9 = Base64.decode( + "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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) "); + 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) "); + 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( + "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/OdzT9JdfrIhMBBARAgAM" + + "BQI/RxooBYMAemL8AAoJEM0j9enEyjRDiBgAn3RcLK+gq90PvnQFTw2DNqdq7KA0" + + "AKCS0EEIXCzbV1tfTdCUJ3hVh3btF7QkRXJpYyBFY2hpZG5hIDxlcmljQGJvdW5j" + + "eWNhc3RsZS5vcmc+iLgEEwECACIFAj9HFA0CGwMFCQCD1gAECwcDAgMVAgMDFgIB" + + "Ah4BAheAAAoJEDVYYUb98/Nzc7oD/j21cUKKI/y+Dvs5e+eZufM8EDUbqMJFk0/X" + + "Ihe6w6OkerIs3kr2HDqWr+jik2IK6KrfaiyoZFfeW/+cN24lTWSfY7D4jZVyL6iM" + + "xd0IZ0So/Bl+/juMcvBGshQnbY46haw5G2C9J5FR3gjODyOu5oUzu5+vmGuMSXVy" + + "4tbUevwuiEwEEBECAAwFAj9HGigFgwB6YvwACgkQzSP16cTKNEPwBQCdHm0Amwza" + + "NmVmDHm3rmqI7rp2oQ0An2YbiP/H/kmBNnmTeH55kd253QOhuIsEP0ccUAEEANCd" + + "gSHam/CbQxio72Ma47tLqyVVc8iNQpwowKewrS6B0CVEFOz2o51eCXddrpH3KCuz" + + "Ea2mMnUBI8YdjKj6bkICUt3YEFa8Tj2qw0GARa2TXdrWcTo2GpIRK71JkaOslhWs" + + "QocKjIJfF1roATYNQ72vv2HKBkPpcviYOEVmaB/PAAYpiJ8EGAECAAkFAj9HHFAC" + + "GwwACgkQNVhhRv3z83M5hQP8DIOR4TeTZKpo5x1BX3lzV3R85npMsN/EZ7gjCpn3" + + "ONwIg7PhbSetoKUOjxLGReKPUuX8XI1ZN1UoNNFzA7UP9AbCgXORsYSU6uxvv0qH" + + "jON91htfbqEtPuS3/99cDaE56b+p6TmmKXE0Oy/Swp88ABeB9ISTxOm4ZltgjHLB" + + "PyGZAaIEP0cHNREEALOTFEJUYmDn4t30NThemd3gsIqAyrtg5jsaLQSAr3aBtprr" + + "7NChAWrPW23dBSanPdhWSb/MsRIEG0LUAIhcaCBR0UxSvkjzv7UJvq/N58C0a3w3" + + "lVXUX8xe/WAnPgG7ofLEv+dx97vWOw2Fq/SLjICN/Z61YWPnfMrpH46ODkfrAKCk" + + "v3+1dV8Y/xKoW/Ks3/jfsyUVJQQAg3dXqP8xSWXKtmCWu2vb5FYjBGsb8AKasXqF" + + "O6A6XhxDFoNg1ye1duGVOUmz0zGNMffHHPKXKKsLKrsfMYnFnNK3oVN6HUMVjOEn" + + "tC5hHXGtJ0DpjSZS/SLEPkDic8eO6lDlAKocZ5F8UP+gfMPjOoGcmn8Encu9Drps" + + "REmYqXkD/i6PcehXGlvS+/IQ19GooyYYEhLJxjgygfm5Z9vbcV9+xP2v0ovqcniz" + + "IvNpFHSC9xtyORz1nd6kjuCveNOtvh3xY/GwNtP9bW2/1sb8QdrCiYtyKG25RTrx" + + "R6Pr/Bmi3Vcmq7adCWS7nv4z9XZfXLbpUqyKmiU9+Nri7IE47K9stDNFcmljIEVj" + + "aGlkbmEgKERTQSBUZXN0IEtleSkgPGVyaWNAYm91bmN5Y2FzdGxlLm9yZz6IWQQT" + + "EQIAGQUCP0cHNQQLBwMCAxUCAwMWAgECHgECF4AACgkQzSP16cTKNEMCXACfauui" + + "bSwyG59Yrm8hHCDuCPmqwsQAni+dPl08FVuWh+wb6kOgJV4lcYae"); + + byte[] subPubCrc = Base64.decode("rikt"); + + byte[] pgp8Key = Base64.decode( + "lQIEBEBXUNMBBADScQczBibewnbCzCswc/9ut8R0fwlltBRxMW0NMdKJY2LF" + + "7k2COeLOCIU95loJGV6ulbpDCXEO2Jyq8/qGw1qD3SCZNXxKs3GS8Iyh9Uwd" + + "VL07nMMYl5NiQRsFB7wOb86+94tYWgvikVA5BRP5y3+O3GItnXnpWSJyREUy" + + "6WI2QQAGKf4JAwIVmnRs4jtTX2DD05zy2mepEQ8bsqVAKIx7lEwvMVNcvg4Y" + + "8vFLh9Mf/uNciwL4Se/ehfKQ/AT0JmBZduYMqRU2zhiBmxj4cXUQ0s36ysj7" + + "fyDngGocDnM3cwPxaTF1ZRBQHSLewP7dqE7M73usFSz8vwD/0xNOHFRLKbsO" + + "RqDlLA1Cg2Yd0wWPS0o7+qqk9ndqrjjSwMM8ftnzFGjShAdg4Ca7fFkcNePP" + + "/rrwIH472FuRb7RbWzwXA4+4ZBdl8D4An0dwtfvAO+jCZSrLjmSpxEOveJxY" + + "GduyR4IA4lemvAG51YHTHd4NXheuEqsIkn1yarwaaj47lFPnxNOElOREMdZb" + + "nkWQb1jfgqO24imEZgrLMkK9bJfoDnlF4k6r6hZOp5FSFvc5kJB4cVo1QJl4" + + "pwCSdoU6luwCggrlZhDnkGCSuQUUW45NE7Br22NGqn4/gHs0KCsWbAezApGj" + + "qYUCfX1bcpPzUMzUlBaD5rz2vPeO58CDtBJ0ZXN0ZXIgPHRlc3RAdGVzdD6I" + + "sgQTAQIAHAUCQFdQ0wIbAwQLBwMCAxUCAwMWAgECHgECF4AACgkQs8JyyQfH" + + "97I1QgP8Cd+35maM2cbWV9iVRO+c5456KDi3oIUSNdPf1NQrCAtJqEUhmMSt" + + "QbdiaFEkPrORISI/2htXruYn0aIpkCfbUheHOu0sef7s6pHmI2kOQPzR+C/j" + + "8D9QvWsPOOso81KU2axUY8zIer64Uzqc4szMIlLw06c8vea27RfgjBpSCryw" + + "AgAA"); + + char[] pgp8Pass = "2002 Buffalo Sabres".toCharArray(); + + char[] pass = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' }; + + byte[] fingerprintKey = Base64.decode( + "mQEPA0CiJdUAAAEIAMI+znDlPd2kQoEcnxqxLcRz56Z7ttFKHpnYp0UkljZdquVc" + + "By1jMfXGVV64xN1IvMcyenLXUE0IUeUBCQs6tHunFRAPSeCxJ3FdFe1B5MpqQG8A" + + "BnEpAds/hAUfRDZD5y/lolk1hjvFMrRh6WXckaA/QQ2t00NmTrJ1pYUpkw9tnVQb" + + "LUjWJhfZDBBcN0ADtATzgkugxMtcDxR6I5x8Ndn+IilqIm23kxGIcmMd/BHOec4c" + + "jRwJXXDb7u8tl+2knAf9cwhPHp3+Zy4uGSQPdzQnXOhBlA+4WDa0RROOevWgq8uq" + + "8/9Xp/OlTVL+OoIzjsI6mJP1Joa4qmqAnaHAmXcAEQEAAbQoQk9BM1JTS1kgPEJP" + + "QSBNb25pdG9yaW5nIEAgODg4LTI2OS01MjY2PokBFQMFEECiJdWqaoCdocCZdwEB" + + "0RsH/3HPxoUZ3G3K7T3jgOnJUckTSHWU3XspHzMVgqOxjTrcexi5IsAM5M+BulfW" + + "T2aO+Kqf5w8cKTKgW02DNpHUiPjHx0nzDE+Do95zbIErGeK+Twkc4O/aVsvU9GGO" + + "81VFI6WMvDQ4CUAUnAdk03MRrzI2nAuhn4NJ5LQS+uJrnqUJ4HmFAz6CQZQKd/kS" + + "Xgq+A6i7aI1LG80YxWa9ooQgaCrb9dwY/kPQ+yC22zQ3FExtv+Fv3VtAKTilO3vn" + + "BA4Y9uTHuObHfI+1yxUS2PrlRUX0m48ZjpIX+cEN3QblGBJudI/A1QSd6P0LZeBr" + + "7F1Z1aF7ZDo0KzgiAIBvgXkeTpw="); + + byte[] fingerprintCheck = Base64.decode("CTv2"); + + byte[] expiry60and30daysSig13Key = Base64.decode( + "mQGiBENZt/URBAC5JccXiwe4g6MuviEC8NI/x0NaVkGFAOY04d5E4jeIycBP" + + "SrpOPrjETuigqhrj8oqed2+2yUqfnK4nhTsTAjyeJ3PpWC1pGAKzJgYmJk+K" + + "9aTLq0BQWiXDdv5RG6fDmeq1umvOfcXBqGFAguLPZC+U872bSLnfe3lqGNA8" + + "jvmY7wCgjhzVQVm10NN5ST8nemPEcSjnBrED/R494gHL6+r5OgUgXnNCDejA" + + "4InoDImQCF+g7epp5E1MB6CMYSg2WSY2jHFuHpwnUb7AiOO0ZZ3UBqM9rYnK" + + "kDvxkFCxba7Ms+aFj9blRNmy3vG4FewDcTdxzCtjUk6dRfu6UoARpqlTE/q7" + + "Xo6EQP1ncwJ+UTlcHkTBvg/usI/yBACGjBqX8glb5VfNaZgNHMeS/UIiUiuV" + + "SVFojiSDOHcnCe/6y4M2gVm38zz1W9qhoLfLpiAOFeL0yj6wzXvsjjXQiKQ8" + + "nBE4Mf+oeH2qiQ/LfzQrGpI5eNcMXrzK9nigmz2htYO2GjQfupEnu1RHBTH8" + + "NjofD2AShL9IO73plRuExrQgVGVzdCBLZXkgPHRlc3RAYm91bmN5Y2FzdGxl" + + "Lm9yZz6IZAQTEQIAJAIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAUCQ1m4DgUJ" + + "AE8aGQAKCRD8QP1QuU7Kqw+eAJ0dZ3ZAqr73X61VmCkbyPoszLQMAQCfdFs2" + + "YMDeUvX34Q/8Ba0KgO5f3RSwAgADuM0EQ1m39hADAIHpVGcLqS9UkmQaWBvH" + + "WP6TnN7Y1Ha0TJOuxpbFjBW+CmVh/FjcsnavFXDXpo2zc742WT+vrHBSa/0D" + + "1QEBsnCaX5SRRVp7Mqs8q+aDhjcHMIP8Sdxf7GozXDORkrRaJwADBQL9HLYm" + + "7Rr5iYWDcvs+Pi6O1zUyb1tjkxEGaV/rcozl2MMmr2mzJ6x/Bz8SuhZEJS0m" + + "bB2CvAA39aQi9jHlV7q0SV73NOkd2L/Vt2UZhzlUdvrJ37PgYDv+Wd9Ufz6g" + + "MzLSiE8EGBECAA8FAkNZt/YCGwwFCQAnjQAACgkQ/ED9ULlOyqsTqQCcDnAZ" + + "7YymCfhm1yJiuFQg3qiX6Z4An19OSEgeSKugVcH49g1sxUB0zNdIsAIAAw=="); + + byte[] jpegImage = Base64.decode( + "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"); + + byte[] embeddedJPEGKey = Base64.decode( + "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" + + "Qvmtc8X7lqK/zLRVkSSBItik2IIhsAIAAw=="); + + + private void fingerPrintTest() + throws Exception + { + // + // version 3 + // + PGPPublicKeyRing pgpPub = new PGPPublicKeyRing(fingerprintKey, new BcKeyFingerprintCalculator()); + + PGPPublicKey pubKey = pgpPub.getPublicKey(); + + if (!areEqual(pubKey.getFingerprint(), Hex.decode("4FFB9F0884266C715D1CEAC804A3BBFA"))) + { + fail("version 3 fingerprint test failed"); + } + + // + // version 4 + // + pgpPub = new PGPPublicKeyRing(testPubKey, new BcKeyFingerprintCalculator()); + + pubKey = pgpPub.getPublicKey(); + + if (!areEqual(pubKey.getFingerprint(), Hex.decode("3062363c1046a01a751946bb35586146fdf3f373"))) + { + fail("version 4 fingerprint test failed"); + } + } + + private void mixedTest(PGPPrivateKey pgpPrivKey, PGPPublicKey pgpPubKey) + throws Exception + { + byte[] text = { (byte)'h', (byte)'e', (byte)'l', (byte)'l', (byte)'o', (byte)' ', (byte)'w', (byte)'o', (byte)'r', (byte)'l', (byte)'d', (byte)'!', (byte)'\n' }; + + // + // literal data + // + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator(); + OutputStream lOut = lGen.open(bOut, PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, text.length, new Date()); + + lOut.write(text); + + lGen.close(); + + byte[] bytes = bOut.toByteArray(); + + PGPObjectFactory f = new PGPObjectFactory(bytes); + checkLiteralData((PGPLiteralData)f.nextObject(), text); + + ByteArrayOutputStream bcOut = new ByteArrayOutputStream(); + + PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_128).setWithIntegrityPacket(true).setSecureRandom(new SecureRandom())); + + encGen.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(pgpPubKey)); + + encGen.addMethod(new BcPBEKeyEncryptionMethodGenerator("password".toCharArray())); + + OutputStream cOut = encGen.open(bcOut, bytes.length); + + cOut.write(bytes); + + cOut.close(); + + byte[] encData = bcOut.toByteArray(); + + // + // asymmetric + // + PGPObjectFactory pgpF = new PGPObjectFactory(encData); + + PGPEncryptedDataList encList = (PGPEncryptedDataList)pgpF.nextObject(); + + PGPPublicKeyEncryptedData encP = (PGPPublicKeyEncryptedData)encList.get(0); + + InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(pgpPrivKey)); + + PGPObjectFactory pgpFact = new PGPObjectFactory(clear); + + checkLiteralData((PGPLiteralData)pgpFact.nextObject(), text); + + // + // PBE + // + pgpF = new PGPObjectFactory(encData); + + encList = (PGPEncryptedDataList)pgpF.nextObject(); + + PGPPBEEncryptedData encPbe = (PGPPBEEncryptedData)encList.get(1); + + clear = encPbe.getDataStream(new BcPBEDataDecryptorFactory("password".toCharArray(), new BcPGPDigestCalculatorProvider())); + + pgpF = new PGPObjectFactory(clear); + + checkLiteralData((PGPLiteralData)pgpF.nextObject(), text); + } + + private void checkLiteralData(PGPLiteralData ld, byte[] data) + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + if (!ld.getFileName().equals(PGPLiteralData.CONSOLE)) + { + throw new RuntimeException("wrong filename in packet"); + } + + InputStream inLd = ld.getDataStream(); + int ch; + + while ((ch = inLd.read()) >= 0) + { + bOut.write(ch); + } + + if (!areEqual(bOut.toByteArray(), data)) + { + fail("wrong plain text in decrypted packet"); + } + } + + private void existingEmbeddedJpegTest() + throws Exception + { + PGPPublicKeyRing pgpPub = new PGPPublicKeyRing(embeddedJPEGKey, new BcKeyFingerprintCalculator()); + + PGPPublicKey pubKey = pgpPub.getPublicKey(); + + Iterator it = pubKey.getUserAttributes(); + int count = 0; + while (it.hasNext()) + { + PGPUserAttributeSubpacketVector attributes = (PGPUserAttributeSubpacketVector)it.next(); + + Iterator sigs = pubKey.getSignaturesForUserAttribute(attributes); + int sigCount = 0; + while (sigs.hasNext()) + { + PGPSignature sig = (PGPSignature)sigs.next(); + + sig.init(new BcPGPContentVerifierBuilderProvider(), pubKey); + + if (!sig.verifyCertification(attributes, pubKey)) + { + fail("signature failed verification"); + } + + sigCount++; + } + + if (sigCount != 1) + { + fail("Failed user attributes signature check"); + } + count++; + } + + if (count != 1) + { + fail("didn't find user attributes"); + } + } + + private void embeddedJpegTest() + throws Exception + { + PGPPublicKeyRing pgpPub = new PGPPublicKeyRing(testPubKey, new BcKeyFingerprintCalculator()); + PGPSecretKeyRing pgpSec = new PGPSecretKeyRing(testPrivKey, new BcKeyFingerprintCalculator()); + + PGPPublicKey pubKey = pgpPub.getPublicKey(); + + PGPUserAttributeSubpacketVectorGenerator vGen = new PGPUserAttributeSubpacketVectorGenerator(); + + vGen.setImageAttribute(ImageAttribute.JPEG, jpegImage); + + PGPUserAttributeSubpacketVector uVec = vGen.generate(); + + PGPSignatureGenerator sGen = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(PublicKeyAlgorithmTags.RSA_GENERAL, HashAlgorithmTags.SHA1)); + + sGen.init(PGPSignature.POSITIVE_CERTIFICATION, pgpSec.getSecretKey().extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass))); + + PGPSignature sig = sGen.generateCertification(uVec, pubKey); + + PGPPublicKey nKey = PGPPublicKey.addCertification(pubKey, uVec, sig); + + Iterator it = nKey.getUserAttributes(); + int count = 0; + while (it.hasNext()) + { + PGPUserAttributeSubpacketVector attributes = (PGPUserAttributeSubpacketVector)it.next(); + + Iterator sigs = nKey.getSignaturesForUserAttribute(attributes); + int sigCount = 0; + while (sigs.hasNext()) + { + PGPSignature s = (PGPSignature)sigs.next(); + + s.init(new BcPGPContentVerifierBuilderProvider(), pubKey); + + if (!s.verifyCertification(attributes, pubKey)) + { + fail("added signature failed verification"); + } + + sigCount++; + } + + if (sigCount != 1) + { + fail("Failed added user attributes signature check"); + } + count++; + } + + if (count != 1) + { + fail("didn't find added user attributes"); + } + + nKey = PGPPublicKey.removeCertification(nKey, uVec); + count = 0; + for (it = nKey.getUserAttributes(); it.hasNext();) + { + count++; + } + if (count != 0) + { + fail("found attributes where none expected"); + } + } + + private void sigsubpacketTest() + throws Exception + { + char[] passPhrase = "test".toCharArray(); + String identity = "TEST "; + 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( + "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"); + + byte[] sec2 = Base64.decode( + "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" + + "Nifs+ljmsAFn"); + + + char[] sec2pass1 = "sandhya".toCharArray(); + char[] sec2pass2 = "psai".toCharArray(); + + byte[] pub3 = Base64.decode( + "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" + + "n3FaE6UZE9GMDmCIAjhI+0X9aH6CsAIAAw=="); + + byte[] sec3 = Base64.decode( + "lQHhBEB9BH0RBACtYQtE7tna6hgGyGLpq+ds3r2cLC0ISn5dNw7tm9vwiNVF" + + "JA2N37RRrifw4PvgelRSvLaX3M3ZBqC9s1Metg3v4FSlIRtSLWCNpHSvNw7i" + + "X8C2Xy9Hdlbh6Y/50o+iscojLRE14upfR1bIkcCZQGSyvGV52V2wBImUUZjV" + + "s2ZngwCg7mu852vK7+euz4WaL7ERVYtq9CMEAJ5swrljerDpz/RQ4Lhp6KER" + + "KyuI0PUttO57xINGshEINgYlZdGaZHRueHe7uKfI19mb0T4N3NJWaZ0wF+Cn" + + "rixsq0VrTUfiwfZeGluNG73aTCeY45fVXMGTTSYXzS8T0LW100Xn/0g9HRyA" + + "xUpuWo8IazxkMqHJis2uwriYKpAfA/9anvj5BS9p5pfPjp9dGM7GTMIYl5f2" + + "fcP57f+AW1TVR6IZiMJAvAdeWuLtwLnJiFpGlnFz273pfl+sAuqm1yNceImR" + + "2SDDP4+vtyycWy8nZhgEuhZx3W3cWMQz5WyNJSY1JJHh9TCQkCoN8E7XpVP4" + + "zEPboB2GzD93mfD8JLHP+/4DAwIvYrn+YqRaaGAu19XUj895g/GROyP8WEaU" + + "Bd/JNqWc4kE/0guetGnPzq7G3bLVwiKfFd4X7BrgHAo3mrQtVGVzdCBLZXkg" + + "KG5vIGNvbW1lbnQpIDx0ZXN0QGJvdW5jeWNhc3RsZS5vcmc+iFkEExECABkF" + + "AkB9BH0ECwcDAgMVAgMDFgIBAh4BAheAAAoJEKnMV8vjZQOpSRQAoKZy6YS1" + + "irF5/Q3JlWiwbkN6dEuLAJ9lldRLOlXsuQ5JW1+SLEc6K9ho4rACAADRwXPB" + + "cQEQAAEBAAAAAAAAAAAAAAAA/9j/4AAQSkZJRgABAQEASABIAAD//gAXQ3Jl" + + "YXRlZCB3aXRoIFRoZSBHSU1Q/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZ" + + "EhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sA" + + "QwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy" + + "MjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAFAAUAwEiAAIRAQMRAf/EABoA" + + "AQACAwEAAAAAAAAAAAAAAAAEBQIDBgf/xAAoEAABAgUDBAEFAAAAAAAAAAAB" + + "AgMABBEhMQUSQQYTIiNhFFGBkcH/xAAXAQEAAwAAAAAAAAAAAAAAAAAEAgMF" + + "/8QAJBEAAQQAAwkAAAAAAAAAAAAAAQACERIEIfATMTJBUZGx0fH/2gAMAwEA" + + "AhEDEQA/APMuotJlJVxstqaPo22NlAUp+YsNO0qSUtBcMu6n6EtOHcfPAHHF" + + "I16++oajQtTA3DapK02HFR8UpE9pTbQWtKm2WG2rlxVyQTcfGbn7Qm0OIjL7" + + "7Wrs2NNm9lzTmmSxQ0PX4opSprk5tmESF6syggzGwOLG6gXgHFbZhBixk8Xl" + + "IDcOQLRKt+rX+3qC5ZLTQblpQlvwvxn9CMpZturVGkJHapQJphRH8hCLXbzr" + + "qpYsCx1zC5rtpJNuYQhASc0UAQv/2YhcBBMRAgAcBQJAfQV+AhsDBAsHAwID" + + "FQIDAxYCAQIeAQIXgAAKCRCpzFfL42UDqfa2AJ9hjtEeDTbTEAuuSbzhYFxN" + + "/qc0FACgsmzysdbBpuN65yK01tbEaeIMtqCwAgAAnQEUBEB9BH4QAwCqRuWO" + + "rxm5v/8WWBtPEUZnYuu3WYxefRKn6ON5ka5Ymp4AfKpKqKqn982tjlG13GVE" + + "P2ow11L4yQKGNm+VyVEapm7SpMD0SHg9vsJygAWrfMJnBYWSgE19W6o3ckUR" + + "sWsAAwYDAKDCSiSxC3Z5/POaCehw5iHs/vmVk5Wn831LSZEEwkSohNDbIEry" + + "ia/9tFXSt11AP4stJtZ0MH87/6JBbiIRD4JXcx+zfzchL7Teczr/06QMphSn" + + "sAENupbhLupfpR+BbP4DAwIvYrn+YqRaaGBjvFK1fbxCt7ZM4I2W/3BC0lCX" + + "m/NypKNspGflec8u96uUlA0fNCnxm6f9nbB0jpvoKi0g4iqAf+P2iEYEGBEC" + + "AAYFAkB9BH4ACgkQqcxXy+NlA6mtMgCgvccZA/Sg7BXVpxli47SYhxSHoM4A" + + "oNCOMplSnYTuh5ikKeBWtz36gC1psAIAAA=="); + + char[] sec3pass1 = "123456".toCharArray(); + + // + // GPG comment packets. + // + byte[] sec4 = Base64.decode( + "lQG7BD0PbK8RBAC0cW4Y2MZXmAmqYp5Txyw0kSQsFvwZKHNMFRv996IsN57URVF5" + + "BGMVPRBi9dNucWbjiSYpiYN13wE9IuLZsvVaQojV4XWGRDc+Rxz9ElsXnsYQ3mZU" + + "7H1bNQEofstChk4z+dlvPBN4GFahrIzn/CeVUn6Ut7dVdYbiTqviANqNXwCglfVA" + + "2OEePvqFnGxs1jhJyPSOnTED/RwRvsLH/k43mk6UEvOyN1RIpBXN+Ieqs7h1gFrQ" + + "kB+WMgeP5ZUsotTffVDSUS9UMxRQggVUW1Xml0geGwQsNfkr/ztWMs/T4xp1v5j+" + + "QyJx6OqNlkGdqOsoqkzJx0SQ1zBxdinFyyC4H95SDAb/RQOu5LQmxFG7quexztMs" + + "infEA/9cVc9+qCo92yRAaXRqKNVVQIQuPxeUsGMyVeJQvJBD4An8KTMCdjpF10Cp" + + "qA3t+n1S0zKr5WRUtvS6y60MOONO+EJWVWBNkx8HJDaIMNkfoqQoz3Krn7w6FE/v" + + "/5uwMd6jY3N3yJZn5nDZT9Yzv9Nx3j+BrY+henRlSU0c6xDc9QAAnjJYg0Z83VJG" + + "6HrBcgc4+4K6lHulCqH9JiM6RFNBX2ZhY3RvcjoAAK9hV206agp99GI6x5qE9+pU" + + "vs6O+Ich/SYjOkRTQV9mYWN0b3I6AACvYAfGn2FGrpBYbjnpTuFOHJMS/T5xg/0m" + + "IzpEU0FfZmFjdG9yOgAAr0dAQz6XxMwxWIn8xIZR/v2iN2L9C6O0EkZvbyBCYXIg" + + "PGJhekBxdXV4PohXBBMRAgAXBQI9D2yvBQsHCgMEAxUDAgMWAgECF4AACgkQUGLI" + + "YCIktfoGogCfZiXMJUKrScqozv5tMwzTTk2AaT8AniM5iRr0Du/Y08SL/NMhtF6H" + + "hJ89nO4EPQ9ssRADAI6Ggxj6ZBfoavuXd/ye99osW8HsNlbqhXObu5mCMNySX2wa" + + "HoWyRUEaUkI9eQw+MlHzIwzA32E7y2mU3OQBKdgLcBg4jxtcWVEg8ESKF9MpFXxl" + + "pExxWrr4DFBfCRcsTwAFEQL9G3OvwJuEZXgx2JSS41D3pG4/qiHYICVa0u3p/14i" + + "cq0kXajIk5ZJ6frCIAHIzuQ3n7jjzr05yR8s/qCrNbBA+nlkVNa/samk+jCzxxxa" + + "cR/Dbh2wkvTFuDFFETwQYLuZAADcDck4YGQAmHivVT2NNDCf/aTz0+CJWl+xRc2l" + + "Qw7D/SQjOkVMR19mYWN0b3I6AACbBnv9m5/bb/pjYAm2PtDp0CysQ9X9JCM6RUxH" + + "X2ZhY3RvcjoAAJsFyHnSmaWguTFf6lJ/j39LtUNtmf0kIzpFTEdfZmFjdG9yOgAA" + + "mwfwMD3LxmWtuCWBE9BptWMNH07Z/SQjOkVMR19mYWN0b3I6AACbBdhBrbSiM4UN" + + "y7khDW2Sk0e4v9mIRgQYEQIABgUCPQ9ssQAKCRBQYshgIiS1+jCMAJ9txwHnb1Kl" + + "6i/fSoDs8SkdM7w48wCdFvPEV0sSxE73073YhBgPZtMWbBo="); + + // + // PGP freeware version 7 + // + byte[] pub5 = Base64.decode( + "mQENBEBrBE4BCACjXVcNIFDQSofaIyZnALb2CRg+WY9uUqgHEEAOlPe03Cs5STM5" + + "HDlNmrh4TdFceJ46rxk1mQOjULES1YfHay8lCIzrD7FX4oj0r4DC14Fs1vXaSar2" + + "1szIpttOw3obL4A1e0p6N4jjsoG7N/pA0fEL0lSw92SoBrMbAheXRg4qNTZvdjOR" + + "grcuOuwgJRvPLtRXlhyLBoyhkd5mmrIDGv8QHJ/UjpeIcRXY9kn9oGXnEYcRbMaU" + + "VwXB4pLzWqz3ZejFI3lOxRWjm760puPOnGYlzSVBxlt2LgzUgSj1Mn+lIpWmAzsa" + + "xEiU4xUwEomQns72yYRZ6D3euNCibcte4SeXABEBAAG0KXBhbGFzaCBrYXNvZGhh" + + "biA8cGthc29kaGFuQHRpYWEtY3JlZi5vcmc+iQEuBBABAgAYBQJAawROCAsBAwkI" + + "BwIKAhkBBRsDAAAAAAoJEOfelumuiOrYqPEH+wYrdP5Tq5j+E5yN1pyCg1rwbSOt" + + "Dka0y0p7Oq/VIGLk692IWPItLEunnBXQtGBcWqklrvogvlhxtf16FgoyScfLJx1e" + + "1cJa+QQnVuH+VOESN6iS9Gp9lUfVOHv74mEMXw0l2Djfy/lnrkAMBatggyGnF9xF" + + "VXOLk1J2WVFm9KUE23o6qdB7RGkf31pN2eA7SWmkdJSkUH7o/QSFBI+UTRZ/IY5P" + + "ZIJpsdiIOqd9YMG/4RoSZuPqNRR6x7BSs8nQVR9bYs4PPlp4GfdRnOcRonoTeJCZ" + + "83RnsraWJnJTg34gRLBcqumhTuFKc8nuCNK98D6zkQESdcHLLTquCOaF5L+5AQ0E" + + "QGsETwEIAOVwNCTaDZvW4dowPbET1bI5UeYY8rAGLYsWSUfgaFv2srMiApyBVltf" + + "i6OLcPjcUCHDBjCv4pwx/C4qcHWb8av4xQIpqQXOpO9NxYE1eZnel/QB7DtH12ZO" + + "nrDNmHtaXlulcKNGe1i1utlFhgzfFx6rWkRL0ENmkTkaQmPY4gTGymJTUhBbsSRq" + + "2ivWqQA1TPwBuda73UgslIAHRd/SUaxjXoLpMbGOTeqzcKGjr5XMPTs7/YgBpWPP" + + "UxMlEQIiU3ia1bxpEhx05k97ceK6TSH2oCPQA7gumjxOSjKT+jEm+8jACVzymEmc" + + "XRy4D5Ztqkw/Z16pvNcu1DI5m6xHwr8AEQEAAYkBIgQYAQIADAUCQGsETwUbDAAA" + + "AAAKCRDn3pbprojq2EynB/4/cEOtKbI5UisUd3vkTzvWOcqWUqGqi5wjjioNtIM5" + + "pur2nFvhQE7SZ+PbAa87HRJU/4WcWMcoLkHD48JrQwHCHOLHSV5muYowb78X4Yh9" + + "epYtSJ0uUahcn4Gp48p4BkhgsPYXkxEImSYzAOWStv21/7WEMqItMYl89BV6Upm8" + + "HyTJx5MPTDbMR7X51hRg3OeQs6po3WTCWRzFIMyGm1rd/VK1L5ZDFPqO3S6YUJ0z" + + "cxecYruvfK0Wp7q834wE8Zkl/PQ3NhfEPL1ZiLr/L00Ty+77/FZqt8SHRCICzOfP" + + "OawcVGI+xHVXW6lijMpB5VaVIH8i2KdBMHXHtduIkPr9"); + + byte[] sec5 = Base64.decode( + "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"); + + char[] sec5pass1 = "12345678".toCharArray(); + + // + // Werner Koch "odd keys" + // + byte[] pub6 = Base64.decode( + "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" + + "L4Kb7TWkd/OHQScVBO8sTUz0+2g="); + + byte[] pub6check = Base64.decode("62O9"); + + // + // revoked sub key + // + byte[] pub7 = Base64.decode( + "mQGiBEFOsIwRBADcjRx7nAs4RaWsQU6p8/ECLZD9sSeYc6CN6UDI96RKj0/hCzMs" + + "qlA0+9fzGZ7ZEJ34nuvDKlhKGC7co5eOiE0a9EijxgcrZU/LClZWa4YfyNg/ri6I" + + "yTyfOfrPQ33GNQt2iImDf3FKp7XKuY9nIxicGQEaW0kkuAmbV3oh0+9q8QCg/+fS" + + "epDEqEE/+nKONULGizKUjMED/RtL6RThRftZ9DOSdBytGYd48z35pca/qZ6HA36K" + + "PVQwi7V77VKQyKFLTOXPLnVyO85hyYB/Nv4DFHN+vcC7/49lfoyYMZlN+LarckHi" + + "NL154wmmzygB/KKysvWBLgkErEBCD0xBDd89iTQNlDtVQAWGORVffl6WWjOAkliG" + + "3dL6A/9A288HfFRnywqi3xddriV6wCPmStC3dkCS4vHk2ofS8uw4ZNoRlp1iEPna" + + "ai2Xa9DX1tkhaGk2k96MqqbBdGpbW8sMA9otJ9xdMjWEm/CgJUFUFQf3zaVy3mkM" + + "S2Lvb6P4Wc2l/diEEIyK8+PqJItSh0OVU3K9oM7ngHwVcalKILQVUkV2b2tlZCA8" + + "UmV2b2tlZEB0ZWQ+iQBOBBARAgAOBQJBTrCMBAsDAgECGQEACgkQvglkcFA/c63+" + + "QgCguh8rsJbPTtbhZcrqBi5Mo1bntLEAoPZQ0Kjmu2knRUpHBeUemHDB6zQeuQIN" + + "BEFOsIwQCAD2Qle3CH8IF3KiutapQvMF6PlTETlPtvFuuUs4INoBp1ajFOmPQFXz" + + "0AfGy0OplK33TGSGSfgMg71l6RfUodNQ+PVZX9x2Uk89PY3bzpnhV5JZzf24rnRP" + + "xfx2vIPFRzBhznzJZv8V+bv9kV7HAarTW56NoKVyOtQa8L9GAFgr5fSI/VhOSdvN" + + "ILSd5JEHNmszbDgNRR0PfIizHHxbLY7288kjwEPwpVsYjY67VYy4XTjTNP18F1dD" + + "ox0YbN4zISy1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6ypUM2Zafq9AKUJsCRtMI" + + "PWakXUGfnHy9iUsiGSa6q6Jew1XpMgs7AAICB/93zriSvSHqsi1FeEmUBo431Jkh" + + "VerIzb6Plb1j6FIq+s3vyvx9K+dMvjotZqylWZj4GXpH+2xLJTjWkrGSfUZVI2Nk" + + "nyOFxUCKLLqaqVBFAQIjULfvQfGEWiGQKk9aRLkdG+D+8Y2N9zYoBXoQ9arvvS/t" + + "4mlOsiuaTe+BZ4x+BXTpF4b9sKZl7V8QP/TkoJWUdydkvxciHdWp7ssqyiKOFRhG" + + "818knDfFQ3cn2w/RnOb+7AF9wDncXDPYLfpPv9b2qZoLrXcyvlLffGDUdWs553ut" + + "1F5AprMURs8BGmY9BnjggfVubHdhTUoA4gVvrdaf+D9NwZAl0xK/5Y/oPuMZiQBG" + + "BBgRAgAGBQJBTrCMAAoJEL4JZHBQP3Ot09gAoMmLKloVDP+WhDXnsM5VikxysZ4+" + + "AKCrJAUO+lYAyPYwEwgK+bKmUGeKrIkARgQoEQIABgUCQU6wpQAKCRC+CWRwUD9z" + + "rQK4AJ98kKFxGU6yhHPr6jYBJPWemTNOXgCfeGB3ox4PXeS4DJDuLy9yllytOjo="); + + byte[] pub7check = Base64.decode("f/YQ"); + + byte[] pub8 = Base64.decode( + "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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"); + + + // 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) "); + 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) "); + 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 "; + 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 "; + 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(); + } + } + } +} + -- cgit v1.2.3