From fbb93e4c275eb7d033bd8cb849d80673d379941e Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 7 Mar 2011 10:17:10 +0000 Subject: [PATCH] Initial checkin of BitCoinJ --- COPYING | 202 +++ README | 12 + TODO | 23 + build.xml | 45 + docs/allclasses-frame.html | 97 ++ docs/allclasses-noframe.html | 97 ++ docs/com/google/bitcoin/core/Address.html | 366 +++++ .../bitcoin/core/AddressFormatException.html | 252 +++ .../google/bitcoin/core/AddressMessage.html | 253 +++ docs/com/google/bitcoin/core/Base58.html | 303 ++++ docs/com/google/bitcoin/core/Block.html | 492 ++++++ docs/com/google/bitcoin/core/BlockChain.html | 328 ++++ docs/com/google/bitcoin/core/ECKey.html | 457 ++++++ .../google/bitcoin/core/GetBlocksMessage.html | 325 ++++ .../google/bitcoin/core/GetDataMessage.html | 309 ++++ .../bitcoin/core/InventoryItem.Type.html | 339 ++++ .../google/bitcoin/core/InventoryItem.html | 326 ++++ .../google/bitcoin/core/InventoryMessage.html | 365 +++++ docs/com/google/bitcoin/core/Message.html | 381 +++++ .../bitcoin/core/NetworkConnection.html | 354 +++++ .../bitcoin/core/NetworkParameters.html | 409 +++++ docs/com/google/bitcoin/core/Peer.html | 365 +++++ docs/com/google/bitcoin/core/PeerAddress.html | 369 +++++ .../bitcoin/core/ProtocolException.html | 268 ++++ docs/com/google/bitcoin/core/Script.html | 620 ++++++++ .../google/bitcoin/core/ScriptException.html | 254 +++ .../bitcoin/core/Transaction.SigHash.html | 349 ++++ docs/com/google/bitcoin/core/Transaction.html | 660 ++++++++ .../google/bitcoin/core/TransactionInput.html | 439 +++++ .../bitcoin/core/TransactionOutPoint.html | 307 ++++ .../bitcoin/core/TransactionOutput.html | 430 +++++ .../google/bitcoin/core/UnknownMessage.html | 320 ++++ docs/com/google/bitcoin/core/Utils.html | 637 ++++++++ docs/com/google/bitcoin/core/VarInt.html | 340 ++++ .../bitcoin/core/VerificationException.html | 238 +++ .../google/bitcoin/core/VersionMessage.html | 419 +++++ docs/com/google/bitcoin/core/Wallet.html | 518 ++++++ .../bitcoin/core/WalletEventListener.html | 219 +++ .../google/bitcoin/core/package-frame.html | 121 ++ .../google/bitcoin/core/package-summary.html | 313 ++++ .../com/google/bitcoin/core/package-tree.html | 181 +++ .../google/bitcoin/examples/PingService.html | 258 +++ .../google/bitcoin/examples/PrivateKeys.html | 262 +++ .../bitcoin/examples/package-frame.html | 34 + .../bitcoin/examples/package-summary.html | 163 ++ .../google/bitcoin/examples/package-tree.html | 151 ++ docs/constant-values.html | 262 +++ docs/deprecated-list.html | 144 ++ docs/help-doc.html | 217 +++ docs/index-all.html | 838 ++++++++++ docs/index.html | 39 + docs/overview-frame.html | 44 + docs/overview-summary.html | 153 ++ docs/overview-tree.html | 180 +++ docs/package-list | 2 + docs/resources/inherit.gif | Bin 0 -> 57 bytes docs/serialized-form.html | 943 +++++++++++ docs/stylesheet.css | 29 + lib/junit-4.8.2.jar | Bin 0 -> 237344 bytes .../google/bitcoin/bouncycastle/LICENSE.java | 63 + .../asn1/ASN1ApplicationSpecificParser.java | 10 + .../bitcoin/bouncycastle/asn1/ASN1Choice.java | 14 + .../bouncycastle/asn1/ASN1Encodable.java | 102 ++ .../asn1/ASN1EncodableVector.java | 14 + .../bouncycastle/asn1/ASN1Generator.java | 15 + .../bouncycastle/asn1/ASN1InputStream.java | 386 +++++ .../bitcoin/bouncycastle/asn1/ASN1Null.java | 38 + .../bitcoin/bouncycastle/asn1/ASN1Object.java | 38 + .../bouncycastle/asn1/ASN1ObjectParser.java | 19 + .../bouncycastle/asn1/ASN1OctetString.java | 135 ++ .../asn1/ASN1OctetStringParser.java | 9 + .../bouncycastle/asn1/ASN1OutputStream.java | 36 + .../asn1/ASN1ParsingException.java | 23 + .../bouncycastle/asn1/ASN1Sequence.java | 217 +++ .../bouncycastle/asn1/ASN1SequenceParser.java | 10 + .../bitcoin/bouncycastle/asn1/ASN1Set.java | 343 ++++ .../bouncycastle/asn1/ASN1SetParser.java | 10 + .../bouncycastle/asn1/ASN1StreamParser.java | 174 ++ .../bouncycastle/asn1/ASN1TaggedObject.java | 210 +++ .../asn1/ASN1TaggedObjectParser.java | 12 + .../asn1/BERApplicationSpecific.java | 10 + .../asn1/BERApplicationSpecificParser.java | 34 + .../asn1/BERConstructedOctetString.java | 144 ++ .../asn1/BERConstructedSequence.java | 37 + .../bitcoin/bouncycastle/asn1/BERFactory.java | 22 + .../bouncycastle/asn1/BERGenerator.java | 100 ++ .../bouncycastle/asn1/BERInputStream.java | 209 +++ .../bitcoin/bouncycastle/asn1/BERNull.java | 30 + .../asn1/BEROctetStringGenerator.java | 102 ++ .../asn1/BEROctetStringParser.java | 44 + .../bouncycastle/asn1/BEROutputStream.java | 36 + .../bouncycastle/asn1/BERSequence.java | 59 + .../asn1/BERSequenceGenerator.java | 41 + .../bouncycastle/asn1/BERSequenceParser.java | 32 + .../bitcoin/bouncycastle/asn1/BERSet.java | 69 + .../bouncycastle/asn1/BERSetParser.java | 32 + .../bouncycastle/asn1/BERTaggedObject.java | 107 ++ .../asn1/BERTaggedObjectParser.java | 123 ++ .../asn1/ConstructedOctetStream.java | 111 ++ .../asn1/DERApplicationSpecific.java | 225 +++ .../bouncycastle/asn1/DERBMPString.java | 126 ++ .../bouncycastle/asn1/DERBitString.java | 266 ++++ .../bitcoin/bouncycastle/asn1/DERBoolean.java | 113 ++ .../asn1/DERConstructedSequence.java | 53 + .../bouncycastle/asn1/DERConstructedSet.java | 79 + .../bouncycastle/asn1/DEREncodable.java | 6 + .../bouncycastle/asn1/DEREncodableVector.java | 38 + .../bouncycastle/asn1/DEREnumerated.java | 102 ++ .../bouncycastle/asn1/DERExternal.java | 267 ++++ .../bouncycastle/asn1/DERExternalParser.java | 39 + .../bitcoin/bouncycastle/asn1/DERFactory.java | 22 + .../bouncycastle/asn1/DERGeneralString.java | 92 ++ .../bouncycastle/asn1/DERGeneralizedTime.java | 314 ++++ .../bouncycastle/asn1/DERGenerator.java | 119 ++ .../bouncycastle/asn1/DERIA5String.java | 174 ++ .../bouncycastle/asn1/DERInputStream.java | 272 ++++ .../bitcoin/bouncycastle/asn1/DERInteger.java | 123 ++ .../bitcoin/bouncycastle/asn1/DERNull.java | 25 + .../bouncycastle/asn1/DERNumericString.java | 177 +++ .../bitcoin/bouncycastle/asn1/DERObject.java | 20 + .../asn1/DERObjectIdentifier.java | 293 ++++ .../bouncycastle/asn1/DEROctetString.java | 37 + .../asn1/DEROctetStringParser.java | 33 + .../bouncycastle/asn1/DEROutputStream.java | 134 ++ .../bouncycastle/asn1/DERPrintableString.java | 204 +++ .../bouncycastle/asn1/DERSequence.java | 80 + .../asn1/DERSequenceGenerator.java | 45 + .../bouncycastle/asn1/DERSequenceParser.java | 32 + .../bitcoin/bouncycastle/asn1/DERSet.java | 100 ++ .../bouncycastle/asn1/DERSetParser.java | 32 + .../bitcoin/bouncycastle/asn1/DERString.java | 9 + .../bouncycastle/asn1/DERT61String.java | 126 ++ .../bouncycastle/asn1/DERTaggedObject.java | 85 + .../bitcoin/bouncycastle/asn1/DERTags.java | 36 + .../bitcoin/bouncycastle/asn1/DERUTCTime.java | 254 +++ .../bouncycastle/asn1/DERUTF8String.java | 109 ++ .../bouncycastle/asn1/DERUniversalString.java | 120 ++ .../bouncycastle/asn1/DERUnknownTag.java | 79 + .../bouncycastle/asn1/DERVisibleString.java | 126 ++ .../asn1/DefiniteLengthInputStream.java | 105 ++ .../asn1/IndefiniteLengthInputStream.java | 110 ++ .../asn1/LazyDERConstructionEnumeration.java | 43 + .../bouncycastle/asn1/LazyDERSequence.java | 75 + .../bouncycastle/asn1/LimitedInputStream.java | 23 + .../bouncycastle/asn1/OIDTokenizer.java | 48 + .../asn1/bc/BCObjectIdentifiers.java | 51 + .../asn1/cmp/CAKeyUpdAnnContent.java | 73 + .../bouncycastle/asn1/cmp/CMPCertificate.java | 62 + .../bouncycastle/asn1/cmp/CRLAnnContent.java | 55 + .../asn1/cmp/CertConfirmContent.java | 54 + .../bouncycastle/asn1/cmp/CertOrEncCert.java | 76 + .../bouncycastle/asn1/cmp/CertRepMessage.java | 96 ++ .../bouncycastle/asn1/cmp/CertResponse.java | 112 ++ .../bouncycastle/asn1/cmp/CertStatus.java | 81 + .../asn1/cmp/CertifiedKeyPair.java | 105 ++ .../bouncycastle/asn1/cmp/Challenge.java | 96 ++ .../asn1/cmp/ErrorMsgContent.java | 100 ++ .../bouncycastle/asn1/cmp/GenMsgContent.java | 54 + .../bouncycastle/asn1/cmp/GenRepContent.java | 54 + .../asn1/cmp/InfoTypeAndValue.java | 116 ++ .../asn1/cmp/KeyRecRepContent.java | 141 ++ .../bouncycastle/asn1/cmp/OOBCertHash.java | 99 ++ .../bouncycastle/asn1/cmp/PBMParameter.java | 89 ++ .../bouncycastle/asn1/cmp/PKIBody.java | 163 ++ .../asn1/cmp/PKIConfirmContent.java | 42 + .../bouncycastle/asn1/cmp/PKIFailureInfo.java | 104 ++ .../bouncycastle/asn1/cmp/PKIFreeText.java | 91 ++ .../bouncycastle/asn1/cmp/PKIHeader.java | 176 +++ .../bouncycastle/asn1/cmp/PKIMessage.java | 101 ++ .../bouncycastle/asn1/cmp/PKIMessages.java | 54 + .../bouncycastle/asn1/cmp/PKIStatus.java | 57 + .../bouncycastle/asn1/cmp/PKIStatusInfo.java | 164 ++ .../asn1/cmp/POPODecKeyChallContent.java | 54 + .../asn1/cmp/POPODecKeyRespContent.java | 55 + .../bouncycastle/asn1/cmp/PollRepContent.java | 82 + .../bouncycastle/asn1/cmp/PollReqContent.java | 69 + .../bouncycastle/asn1/cmp/ProtectedPart.java | 64 + .../bouncycastle/asn1/cmp/RevAnnContent.java | 103 ++ .../bouncycastle/asn1/cmp/RevDetails.java | 77 + .../bouncycastle/asn1/cmp/RevRepContent.java | 136 ++ .../bouncycastle/asn1/cmp/RevReqContent.java | 54 + .../bouncycastle/asn1/cms/Attribute.java | 82 + .../bouncycastle/asn1/cms/AttributeTable.java | 173 ++ .../asn1/cms/AuthEnvelopedData.java | 218 +++ .../asn1/cms/AuthEnvelopedDataParser.java | 157 ++ .../asn1/cms/AuthenticatedData.java | 288 ++++ .../asn1/cms/AuthenticatedDataParser.java | 178 +++ .../bouncycastle/asn1/cms/CMSAttributes.java | 13 + .../asn1/cms/CMSObjectIdentifiers.java | 17 + .../bouncycastle/asn1/cms/CompressedData.java | 110 ++ .../asn1/cms/CompressedDataParser.java | 48 + .../bouncycastle/asn1/cms/ContentInfo.java | 90 ++ .../asn1/cms/ContentInfoParser.java | 48 + .../asn1/cms/EncryptedContentInfo.java | 106 ++ .../asn1/cms/EncryptedContentInfoParser.java | 51 + .../bouncycastle/asn1/cms/EncryptedData.java | 94 ++ .../bouncycastle/asn1/cms/EnvelopedData.java | 179 +++ .../asn1/cms/EnvelopedDataParser.java | 118 ++ .../asn1/cms/IssuerAndSerialNumber.java | 77 + .../bouncycastle/asn1/cms/KEKIdentifier.java | 139 ++ .../asn1/cms/KEKRecipientInfo.java | 121 ++ .../asn1/cms/KeyAgreeRecipientIdentifier.java | 103 ++ .../asn1/cms/KeyAgreeRecipientInfo.java | 151 ++ .../asn1/cms/KeyTransRecipientInfo.java | 114 ++ .../asn1/cms/OriginatorIdentifierOrKey.java | 165 ++ .../bouncycastle/asn1/cms/OriginatorInfo.java | 129 ++ .../asn1/cms/OriginatorPublicKey.java | 100 ++ .../asn1/cms/OtherKeyAttribute.java | 82 + .../asn1/cms/OtherRecipientInfo.java | 98 ++ .../asn1/cms/PasswordRecipientInfo.java | 143 ++ .../asn1/cms/RecipientEncryptedKey.java | 99 ++ .../asn1/cms/RecipientIdentifier.java | 98 ++ .../bouncycastle/asn1/cms/RecipientInfo.java | 154 ++ .../asn1/cms/RecipientKeyIdentifier.java | 139 ++ .../bouncycastle/asn1/cms/SignedData.java | 306 ++++ .../asn1/cms/SignedDataParser.java | 139 ++ .../asn1/cms/SignerIdentifier.java | 98 ++ .../bouncycastle/asn1/cms/SignerInfo.java | 183 +++ .../bitcoin/bouncycastle/asn1/cms/Time.java | 128 ++ .../asn1/cms/ecc/MQVuserKeyingMaterial.java | 112 ++ .../asn1/crmf/AttributeTypeAndValue.java | 64 + .../bouncycastle/asn1/crmf/CertId.java | 71 + .../asn1/crmf/CertReqMessages.java | 54 + .../bouncycastle/asn1/crmf/CertReqMsg.java | 110 ++ .../bouncycastle/asn1/crmf/CertRequest.java | 80 + .../bouncycastle/asn1/crmf/CertTemplate.java | 134 ++ .../bouncycastle/asn1/crmf/Controls.java | 54 + .../asn1/crmf/EncryptedValue.java | 113 ++ .../asn1/crmf/OptionalValidity.java | 77 + .../asn1/crmf/PKIPublicationInfo.java | 81 + .../bouncycastle/asn1/crmf/POPOPrivKey.java | 41 + .../asn1/crmf/POPOSigningKey.java | 84 + .../asn1/crmf/POPOSigningKeyInput.java | 67 + .../asn1/crmf/ProofOfPossession.java | 78 + .../bouncycastle/asn1/crmf/SinglePubInfo.java | 72 + .../cryptopro/CryptoProObjectIdentifiers.java | 45 + .../asn1/cryptopro/ECGOST3410NamedCurves.java | 168 ++ .../ECGOST3410ParamSetParameters.java | 99 ++ .../asn1/cryptopro/GOST28147Parameters.java | 72 + .../cryptopro/GOST3410NamedParameters.java | 116 ++ .../cryptopro/GOST3410ParamSetParameters.java | 105 ++ .../GOST3410PublicKeyAlgParameters.java | 101 ++ .../asn1/eac/EACObjectIdentifiers.java | 55 + .../asn1/esf/CommitmentTypeIdentifier.java | 14 + .../asn1/esf/CommitmentTypeIndication.java | 83 + .../asn1/esf/CommitmentTypeQualifier.java | 108 ++ .../bouncycastle/asn1/esf/ESFAttributes.java | 21 + .../asn1/esf/OtherHashAlgAndValue.java | 78 + .../bouncycastle/asn1/esf/SPUserNotice.java | 94 ++ .../bitcoin/bouncycastle/asn1/esf/SPuri.java | 47 + .../asn1/esf/SigPolicyQualifierInfo.java | 71 + .../asn1/esf/SigPolicyQualifiers.java | 75 + .../asn1/esf/SignaturePolicyId.java | 100 ++ .../asn1/esf/SignaturePolicyIdentifier.java | 74 + .../asn1/esf/SignerAttribute.java | 97 ++ .../bouncycastle/asn1/esf/SignerLocation.java | 159 ++ .../bouncycastle/asn1/ess/ContentHints.java | 96 ++ .../asn1/ess/ContentIdentifier.java | 65 + .../bouncycastle/asn1/ess/ESSCertID.java | 97 ++ .../bouncycastle/asn1/ess/ESSCertIDv2.java | 138 ++ .../bouncycastle/asn1/ess/OtherCertID.java | 138 ++ .../asn1/ess/OtherSigningCertificate.java | 111 ++ .../asn1/ess/SigningCertificate.java | 111 ++ .../asn1/ess/SigningCertificateV2.java | 132 ++ .../asn1/gnu/GNUObjectIdentifiers.java | 30 + .../asn1/iana/IANAObjectIdentifiers.java | 20 + .../bouncycastle/asn1/icao/DataGroupHash.java | 100 ++ .../asn1/icao/ICAOObjectIdentifiers.java | 15 + .../asn1/icao/LDSSecurityObject.java | 125 ++ .../isismtt/ISISMTTObjectIdentifiers.java | 180 +++ .../asn1/isismtt/ocsp/CertHash.java | 124 ++ .../isismtt/ocsp/RequestedCertificate.java | 183 +++ .../x509/AdditionalInformationSyntax.java | 70 + .../asn1/isismtt/x509/AdmissionSyntax.java | 280 ++++ .../asn1/isismtt/x509/Admissions.java | 188 +++ .../isismtt/x509/DeclarationOfMajority.java | 164 ++ .../asn1/isismtt/x509/MonetaryLimit.java | 131 ++ .../asn1/isismtt/x509/NamingAuthority.java | 225 +++ .../asn1/isismtt/x509/ProcurationSyntax.java | 240 +++ .../asn1/isismtt/x509/ProfessionInfo.java | 407 +++++ .../asn1/isismtt/x509/Restriction.java | 82 + .../asn1/kisa/KISAObjectIdentifiers.java | 9 + .../microsoft/MicrosoftObjectIdentifiers.java | 17 + .../asn1/misc/CAST5CBCParameters.java | 71 + .../bouncycastle/asn1/misc/IDEACBCPar.java | 75 + .../asn1/misc/MiscObjectIdentifiers.java | 46 + .../asn1/misc/NetscapeCertType.java | 54 + .../asn1/misc/NetscapeRevocationURL.java | 18 + .../asn1/misc/VerisignCzagExtension.java | 18 + .../asn1/mozilla/PublicKeyAndChallenge.java | 63 + .../asn1/nist/NISTNamedCurves.java | 96 ++ .../asn1/nist/NISTObjectIdentifiers.java | 56 + .../asn1/ntt/NTTObjectIdentifiers.java | 17 + .../asn1/ocsp/BasicOCSPResponse.java | 112 ++ .../bouncycastle/asn1/ocsp/CertID.java | 105 ++ .../bouncycastle/asn1/ocsp/CertStatus.java | 105 ++ .../bitcoin/bouncycastle/asn1/ocsp/CrlID.java | 86 + .../asn1/ocsp/OCSPObjectIdentifiers.java | 22 + .../bouncycastle/asn1/ocsp/OCSPRequest.java | 90 ++ .../bouncycastle/asn1/ocsp/OCSPResponse.java | 92 ++ .../asn1/ocsp/OCSPResponseStatus.java | 40 + .../bouncycastle/asn1/ocsp/Request.java | 91 ++ .../bouncycastle/asn1/ocsp/ResponderID.java | 83 + .../bouncycastle/asn1/ocsp/ResponseBytes.java | 82 + .../bouncycastle/asn1/ocsp/ResponseData.java | 164 ++ .../bouncycastle/asn1/ocsp/RevokedInfo.java | 92 ++ .../asn1/ocsp/ServiceLocator.java | 36 + .../bouncycastle/asn1/ocsp/Signature.java | 111 ++ .../asn1/ocsp/SingleResponse.java | 143 ++ .../bouncycastle/asn1/ocsp/TBSRequest.java | 154 ++ .../asn1/oiw/ElGamalParameter.java | 49 + .../asn1/oiw/OIWObjectIdentifiers.java | 31 + .../bouncycastle/asn1/pkcs/Attribute.java | 82 + .../asn1/pkcs/AuthenticatedSafe.java | 47 + .../bouncycastle/asn1/pkcs/CertBag.java | 53 + .../asn1/pkcs/CertificationRequest.java | 91 ++ .../asn1/pkcs/CertificationRequestInfo.java | 129 ++ .../bouncycastle/asn1/pkcs/ContentInfo.java | 90 ++ .../bouncycastle/asn1/pkcs/DHParameter.java | 88 ++ .../bouncycastle/asn1/pkcs/EncryptedData.java | 104 ++ .../asn1/pkcs/EncryptedPrivateKeyInfo.java | 86 + .../asn1/pkcs/EncryptionScheme.java | 38 + .../asn1/pkcs/IssuerAndSerialNumber.java | 76 + .../asn1/pkcs/KeyDerivationFunc.java | 23 + .../bouncycastle/asn1/pkcs/MacData.java | 106 ++ .../asn1/pkcs/PBES2Algorithms.java | 77 + .../asn1/pkcs/PBES2Parameters.java | 55 + .../bouncycastle/asn1/pkcs/PBKDF2Params.java | 98 ++ .../asn1/pkcs/PKCS12PBEParams.java | 69 + .../asn1/pkcs/PKCSObjectIdentifiers.java | 248 +++ .../bitcoin/bouncycastle/asn1/pkcs/Pfx.java | 71 + .../asn1/pkcs/PrivateKeyInfo.java | 144 ++ .../asn1/pkcs/RC2CBCParameter.java | 89 ++ .../asn1/pkcs/RSAESOAEPparams.java | 151 ++ .../asn1/pkcs/RSAPrivateKeyStructure.java | 186 +++ .../asn1/pkcs/RSASSAPSSparams.java | 170 ++ .../bouncycastle/asn1/pkcs/SafeBag.java | 78 + .../bouncycastle/asn1/pkcs/SignedData.java | 166 ++ .../bouncycastle/asn1/pkcs/SignerInfo.java | 168 ++ .../asn1/sec/ECPrivateKeyStructure.java | 128 ++ .../bouncycastle/asn1/sec/SECNamedCurves.java | 1029 ++++++++++++ .../asn1/sec/SECObjectIdentifiers.java | 50 + .../asn1/smime/SMIMEAttributes.java | 10 + .../asn1/smime/SMIMECapabilities.java | 115 ++ .../smime/SMIMECapabilitiesAttribute.java | 16 + .../asn1/smime/SMIMECapability.java | 103 ++ .../asn1/smime/SMIMECapabilityVector.java | 51 + ...SMIMEEncryptionKeyPreferenceAttribute.java | 48 + .../asn1/teletrust/TeleTrusTNamedCurves.java | 351 ++++ .../teletrust/TeleTrusTObjectIdentifiers.java | 42 + .../bouncycastle/asn1/tsp/Accuracy.java | 174 ++ .../bouncycastle/asn1/tsp/MessageImprint.java | 77 + .../bouncycastle/asn1/tsp/TSTInfo.java | 254 +++ .../bouncycastle/asn1/tsp/TimeStampReq.java | 181 +++ .../bouncycastle/asn1/tsp/TimeStampResp.java | 86 + .../bouncycastle/asn1/util/ASN1Dump.java | 473 ++++++ .../bouncycastle/asn1/util/DERDump.java | 41 + .../bitcoin/bouncycastle/asn1/util/Dump.java | 22 + .../asn1/x500/DirectoryString.java | 125 ++ .../asn1/x509/AccessDescription.java | 98 ++ .../asn1/x509/AlgorithmIdentifier.java | 126 ++ .../bouncycastle/asn1/x509/AttCertIssuer.java | 90 ++ .../asn1/x509/AttCertValidityPeriod.java | 84 + .../bouncycastle/asn1/x509/Attribute.java | 87 + .../asn1/x509/AttributeCertificate.java | 94 ++ .../asn1/x509/AttributeCertificateInfo.java | 165 ++ .../asn1/x509/AuthorityInformationAccess.java | 106 ++ .../asn1/x509/AuthorityKeyIdentifier.java | 231 +++ .../asn1/x509/BasicConstraints.java | 181 +++ .../bouncycastle/asn1/x509/CRLDistPoint.java | 100 ++ .../bouncycastle/asn1/x509/CRLNumber.java | 32 + .../bouncycastle/asn1/x509/CRLReason.java | 111 ++ .../bouncycastle/asn1/x509/CertPolicyId.java | 20 + .../asn1/x509/CertificateList.java | 126 ++ .../asn1/x509/CertificatePair.java | 169 ++ .../asn1/x509/CertificatePolicies.java | 147 ++ .../bouncycastle/asn1/x509/DSAParameter.java | 92 ++ .../bouncycastle/asn1/x509/DigestInfo.java | 86 + .../bouncycastle/asn1/x509/DisplayText.java | 165 ++ .../asn1/x509/DistributionPoint.java | 158 ++ .../asn1/x509/DistributionPointName.java | 149 ++ .../asn1/x509/ExtendedKeyUsage.java | 128 ++ .../bouncycastle/asn1/x509/GeneralName.java | 424 +++++ .../bouncycastle/asn1/x509/GeneralNames.java | 95 ++ .../asn1/x509/GeneralSubtree.java | 200 +++ .../bouncycastle/asn1/x509/Holder.java | 242 +++ .../asn1/x509/IetfAttrSyntax.java | 174 ++ .../bouncycastle/asn1/x509/IssuerSerial.java | 106 ++ .../asn1/x509/IssuingDistributionPoint.java | 256 +++ .../bouncycastle/asn1/x509/KeyPurposeId.java | 119 ++ .../bouncycastle/asn1/x509/KeyUsage.java | 77 + .../asn1/x509/NameConstraints.java | 104 ++ .../asn1/x509/NoticeReference.java | 155 ++ .../asn1/x509/ObjectDigestInfo.java | 192 +++ .../asn1/x509/PolicyInformation.java | 87 + .../asn1/x509/PolicyMappings.java | 68 + .../asn1/x509/PolicyQualifierId.java | 31 + .../asn1/x509/PolicyQualifierInfo.java | 114 ++ .../asn1/x509/PrivateKeyUsagePeriod.java | 89 ++ .../asn1/x509/RSAPublicKeyStructure.java | 95 ++ .../bouncycastle/asn1/x509/ReasonFlags.java | 85 + .../bouncycastle/asn1/x509/RoleSyntax.java | 236 +++ .../asn1/x509/SubjectDirectoryAttributes.java | 144 ++ .../asn1/x509/SubjectKeyIdentifier.java | 137 ++ .../asn1/x509/SubjectPublicKeyInfo.java | 126 ++ .../bouncycastle/asn1/x509/TBSCertList.java | 266 ++++ .../asn1/x509/TBSCertificateStructure.java | 193 +++ .../bouncycastle/asn1/x509/Target.java | 138 ++ .../asn1/x509/TargetInformation.java | 121 ++ .../bouncycastle/asn1/x509/Targets.java | 122 ++ .../bitcoin/bouncycastle/asn1/x509/Time.java | 133 ++ .../bouncycastle/asn1/x509/UserNotice.java | 117 ++ .../asn1/x509/V1TBSCertificateGenerator.java | 125 ++ .../V2AttributeCertificateInfoGenerator.java | 148 ++ .../bouncycastle/asn1/x509/V2Form.java | 130 ++ .../asn1/x509/V2TBSCertListGenerator.java | 213 +++ .../asn1/x509/V3TBSCertificateGenerator.java | 183 +++ .../asn1/x509/X509Attributes.java | 8 + .../asn1/x509/X509CertificateStructure.java | 132 ++ .../asn1/x509/X509DefaultEntryConverter.java | 65 + .../bouncycastle/asn1/x509/X509Extension.java | 87 + .../asn1/x509/X509Extensions.java | 393 +++++ .../asn1/x509/X509ExtensionsGenerator.java | 93 ++ .../bouncycastle/asn1/x509/X509Name.java | 1256 +++++++++++++++ .../asn1/x509/X509NameEntryConverter.java | 113 ++ .../asn1/x509/X509NameTokenizer.java | 99 ++ .../asn1/x509/X509ObjectIdentifiers.java | 62 + .../asn1/x509/qualified/BiometricData.java | 124 ++ .../qualified/ETSIQCObjectIdentifiers.java | 16 + .../x509/qualified/Iso4217CurrencyCode.java | 93 ++ .../asn1/x509/qualified/MonetaryValue.java | 92 ++ .../asn1/x509/qualified/QCStatement.java | 95 ++ .../qualified/RFC3739QCObjectIdentifiers.java | 14 + .../x509/qualified/SemanticsInformation.java | 130 ++ .../x509/qualified/TypeOfBiometricData.java | 90 ++ .../asn1/x509/sigi/NameOrPseudonym.java | 191 +++ .../asn1/x509/sigi/PersonalData.java | 214 +++ .../asn1/x509/sigi/SigIObjectIdentifiers.java | 45 + .../bouncycastle/asn1/x9/KeySpecificInfo.java | 68 + .../bouncycastle/asn1/x9/OtherInfo.java | 96 ++ .../bouncycastle/asn1/x9/X962NamedCurves.java | 621 ++++++++ .../bouncycastle/asn1/x9/X962Parameters.java | 86 + .../bitcoin/bouncycastle/asn1/x9/X9Curve.java | 161 ++ .../bouncycastle/asn1/x9/X9ECParameters.java | 161 ++ .../asn1/x9/X9ECParametersHolder.java | 18 + .../bouncycastle/asn1/x9/X9ECPoint.java | 48 + .../bouncycastle/asn1/x9/X9FieldElement.java | 64 + .../bouncycastle/asn1/x9/X9FieldID.java | 109 ++ .../asn1/x9/X9IntegerConverter.java | 47 + .../asn1/x9/X9ObjectIdentifiers.java | 142 ++ .../bouncycastle/bcpg/ArmoredInputStream.java | 471 ++++++ .../bcpg/ArmoredOutputStream.java | 410 +++++ .../bouncycastle/bcpg/BCPGInputStream.java | 385 +++++ .../bitcoin/bouncycastle/bcpg/BCPGKey.java | 24 + .../bitcoin/bouncycastle/bcpg/BCPGObject.java | 24 + .../bouncycastle/bcpg/BCPGOutputStream.java | 360 +++++ .../bitcoin/bouncycastle/bcpg/CRC24.java | 37 + .../bcpg/CompressedDataPacket.java | 31 + .../bcpg/CompressionAlgorithmTags.java | 12 + .../bouncycastle/bcpg/ContainedPacket.java | 25 + .../bouncycastle/bcpg/DSAPublicBCPGKey.java | 116 ++ .../bouncycastle/bcpg/DSASecretBCPGKey.java | 82 + .../bcpg/ElGamalPublicBCPGKey.java | 93 ++ .../bcpg/ElGamalSecretBCPGKey.java | 79 + .../bouncycastle/bcpg/ExperimentalPacket.java | 64 + .../bouncycastle/bcpg/HashAlgorithmTags.java | 20 + .../bouncycastle/bcpg/InputStreamPacket.java | 26 + .../bouncycastle/bcpg/LiteralDataPacket.java | 74 + .../bitcoin/bouncycastle/bcpg/MPInteger.java | 62 + .../bouncycastle/bcpg/MarkerPacket.java | 28 + .../bcpg/ModDetectionCodePacket.java | 45 + .../bcpg/OnePassSignaturePacket.java | 115 ++ .../bouncycastle/bcpg/OutputStreamPacket.java | 18 + .../bitcoin/bouncycastle/bcpg/Packet.java | 9 + .../bitcoin/bouncycastle/bcpg/PacketTags.java | 31 + .../bcpg/PublicKeyAlgorithmTags.java | 29 + .../bcpg/PublicKeyEncSessionPacket.java | 112 ++ .../bouncycastle/bcpg/PublicKeyPacket.java | 126 ++ .../bouncycastle/bcpg/PublicSubkeyPacket.java | 40 + .../bouncycastle/bcpg/RSAPublicBCPGKey.java | 91 ++ .../bouncycastle/bcpg/RSASecretBCPGKey.java | 176 +++ .../google/bitcoin/bouncycastle/bcpg/S2K.java | 149 ++ .../bouncycastle/bcpg/SecretKeyPacket.java | 189 +++ .../bouncycastle/bcpg/SecretSubkeyPacket.java | 58 + .../bouncycastle/bcpg/SignaturePacket.java | 515 ++++++ .../bouncycastle/bcpg/SignatureSubpacket.java | 80 + .../bcpg/SignatureSubpacketInputStream.java | 123 ++ .../bcpg/SignatureSubpacketTags.java | 32 + .../bcpg/SymmetricEncDataPacket.java | 14 + .../bcpg/SymmetricEncIntegrityPacket.java | 20 + .../bcpg/SymmetricKeyAlgorithmTags.java | 19 + .../bcpg/SymmetricKeyEncSessionPacket.java | 94 ++ .../bouncycastle/bcpg/TrustPacket.java | 48 + .../bcpg/UserAttributePacket.java | 60 + .../bcpg/UserAttributeSubpacket.java | 91 ++ .../UserAttributeSubpacketInputStream.java | 116 ++ .../bcpg/UserAttributeSubpacketTags.java | 9 + .../bouncycastle/bcpg/UserIDPacket.java | 40 + .../bcpg/attr/ImageAttribute.java | 77 + .../bcpg/sig/EmbeddedSignature.java | 18 + .../bouncycastle/bcpg/sig/Exportable.java | 46 + .../bouncycastle/bcpg/sig/IssuerKeyID.java | 50 + .../bcpg/sig/KeyExpirationTime.java | 50 + .../bouncycastle/bcpg/sig/KeyFlags.java | 73 + .../bouncycastle/bcpg/sig/NotationData.java | 104 ++ .../bcpg/sig/PreferredAlgorithms.java | 59 + .../bouncycastle/bcpg/sig/PrimaryUserID.java | 46 + .../bouncycastle/bcpg/sig/Revocable.java | 46 + .../bcpg/sig/SignatureCreationTime.java | 48 + .../bcpg/sig/SignatureExpirationTime.java | 48 + .../bouncycastle/bcpg/sig/SignerUserID.java | 50 + .../bouncycastle/bcpg/sig/TrustSignature.java | 48 + .../crypto/AsymmetricBlockCipher.java | 45 + .../crypto/AsymmetricCipherKeyPair.java | 44 + .../AsymmetricCipherKeyPairGenerator.java | 22 + .../bouncycastle/crypto/BasicAgreement.java | 21 + .../bouncycastle/crypto/BlockCipher.java | 56 + .../crypto/BufferedAsymmetricBlockCipher.java | 171 ++ .../crypto/BufferedBlockCipher.java | 307 ++++ .../crypto/CipherKeyGenerator.java | 38 + .../bouncycastle/crypto/CipherParameters.java | 8 + .../bouncycastle/crypto/CryptoException.java | 26 + .../bitcoin/bouncycastle/crypto/DSA.java | 36 + .../crypto/DataLengthException.java | 29 + .../crypto/DerivationFunction.java | 17 + .../crypto/DerivationParameters.java | 8 + .../bitcoin/bouncycastle/crypto/Digest.java | 51 + .../bouncycastle/crypto/ExtendedDigest.java | 13 + .../crypto/InvalidCipherTextException.java | 27 + .../crypto/KeyGenerationParameters.java | 48 + .../bitcoin/bouncycastle/crypto/Mac.java | 71 + .../crypto/MaxBytesExceededException.java | 27 + .../crypto/PBEParametersGenerator.java | 157 ++ .../crypto/RuntimeCryptoException.java | 26 + .../bitcoin/bouncycastle/crypto/Signer.java | 43 + .../crypto/SignerWithRecovery.java | 23 + .../crypto/StreamBlockCipher.java | 108 ++ .../bouncycastle/crypto/StreamCipher.java | 53 + .../bitcoin/bouncycastle/crypto/Wrapper.java | 18 + .../crypto/agreement/DHAgreement.java | 94 ++ .../crypto/agreement/DHBasicAgreement.java | 66 + .../crypto/agreement/ECDHBasicAgreement.java | 47 + .../crypto/agreement/ECDHCBasicAgreement.java | 54 + .../crypto/agreement/ECMQVBasicAgreement.java | 86 + .../crypto/agreement/kdf/DHKDFParameters.java | 56 + .../crypto/agreement/kdf/DHKEKGenerator.java | 132 ++ .../agreement/kdf/ECDHKEKGenerator.java | 74 + .../crypto/agreement/srp/SRP6Client.java | 93 ++ .../crypto/agreement/srp/SRP6Server.java | 90 ++ .../crypto/agreement/srp/SRP6Util.java | 91 ++ .../agreement/srp/SRP6VerifierGenerator.java | 47 + .../crypto/digests/GOST3411Digest.java | 344 ++++ .../crypto/digests/GeneralDigest.java | 135 ++ .../crypto/digests/LongDigest.java | 354 +++++ .../crypto/digests/MD2Digest.java | 237 +++ .../crypto/digests/MD4Digest.java | 270 ++++ .../crypto/digests/MD5Digest.java | 302 ++++ .../crypto/digests/RIPEMD128Digest.java | 461 ++++++ .../crypto/digests/RIPEMD160Digest.java | 422 +++++ .../crypto/digests/RIPEMD256Digest.java | 476 ++++++ .../crypto/digests/RIPEMD320Digest.java | 461 ++++++ .../crypto/digests/SHA1Digest.java | 290 ++++ .../crypto/digests/SHA224Digest.java | 292 ++++ .../crypto/digests/SHA256Digest.java | 295 ++++ .../crypto/digests/SHA384Digest.java | 87 + .../crypto/digests/SHA512Digest.java | 89 ++ .../crypto/digests/ShortenedDigest.java | 80 + .../crypto/digests/TigerDigest.java | 866 ++++++++++ .../crypto/digests/WhirlpoolDigest.java | 396 +++++ .../crypto/encodings/ISO9796d1Encoding.java | 251 +++ .../crypto/encodings/OAEPEncoding.java | 348 ++++ .../crypto/encodings/PKCS1Encoding.java | 247 +++ .../crypto/engines/AESEngine.java | 547 +++++++ .../crypto/engines/AESFastEngine.java | 876 ++++++++++ .../crypto/engines/AESLightEngine.java | 440 ++++++ .../crypto/engines/AESWrapEngine.java | 16 + .../crypto/engines/BlowfishEngine.java | 576 +++++++ .../crypto/engines/CAST5Engine.java | 830 ++++++++++ .../crypto/engines/CAST6Engine.java | 296 ++++ .../crypto/engines/CamelliaEngine.java | 683 ++++++++ .../crypto/engines/CamelliaLightEngine.java | 591 +++++++ .../crypto/engines/CamelliaWrapEngine.java | 15 + .../crypto/engines/DESEngine.java | 494 ++++++ .../crypto/engines/DESedeEngine.java | 126 ++ .../crypto/engines/DESedeWrapEngine.java | 348 ++++ .../crypto/engines/ElGamalEngine.java | 217 +++ .../crypto/engines/GOST28147Engine.java | 363 +++++ .../crypto/engines/Grain128Engine.java | 302 ++++ .../crypto/engines/Grainv1Engine.java | 288 ++++ .../crypto/engines/HC128Engine.java | 256 +++ .../crypto/engines/HC256Engine.java | 243 +++ .../crypto/engines/IDEAEngine.java | 366 +++++ .../crypto/engines/IESEngine.java | 257 +++ .../crypto/engines/ISAACEngine.java | 245 +++ .../crypto/engines/NaccacheSternEngine.java | 438 +++++ .../crypto/engines/NoekeonEngine.java | 262 +++ .../crypto/engines/NullEngine.java | 84 + .../crypto/engines/RC2Engine.java | 316 ++++ .../crypto/engines/RC2WrapEngine.java | 383 +++++ .../crypto/engines/RC4Engine.java | 143 ++ .../crypto/engines/RC532Engine.java | 287 ++++ .../crypto/engines/RC564Engine.java | 288 ++++ .../crypto/engines/RC6Engine.java | 362 +++++ .../crypto/engines/RFC3211WrapEngine.java | 175 ++ .../crypto/engines/RFC3394WrapEngine.java | 177 +++ .../crypto/engines/RSABlindedEngine.java | 126 ++ .../crypto/engines/RSABlindingEngine.java | 137 ++ .../crypto/engines/RSACoreEngine.java | 203 +++ .../crypto/engines/RSAEngine.java | 78 + .../crypto/engines/RijndaelEngine.java | 724 +++++++++ .../crypto/engines/SEEDEngine.java | 345 ++++ .../crypto/engines/SEEDWrapEngine.java | 15 + .../crypto/engines/Salsa20Engine.java | 362 +++++ .../crypto/engines/SerpentEngine.java | 782 +++++++++ .../crypto/engines/SkipjackEngine.java | 259 +++ .../crypto/engines/TEAEngine.java | 178 +++ .../crypto/engines/TwofishEngine.java | 677 ++++++++ .../crypto/engines/VMPCEngine.java | 138 ++ .../crypto/engines/VMPCKSA3Engine.java | 45 + .../crypto/engines/XTEAEngine.java | 182 +++ .../crypto/examples/DESExample.java | 419 +++++ .../generators/BaseKDFBytesGenerator.java | 142 ++ .../crypto/generators/DESKeyGenerator.java | 48 + .../crypto/generators/DESedeKeyGenerator.java | 56 + .../generators/DHBasicKeyPairGenerator.java | 42 + .../generators/DHKeyGeneratorHelper.java | 51 + .../crypto/generators/DHKeyPairGenerator.java | 42 + .../generators/DHParametersGenerator.java | 52 + .../crypto/generators/DHParametersHelper.java | 70 + .../generators/DSAKeyPairGenerator.java | 61 + .../generators/DSAParametersGenerator.java | 337 ++++ .../crypto/generators/ECKeyPairGenerator.java | 53 + .../generators/ElGamalKeyPairGenerator.java | 44 + .../ElGamalParametersGenerator.java | 43 + .../generators/GOST3410KeyPairGenerator.java | 57 + .../GOST3410ParametersGenerator.java | 541 +++++++ .../crypto/generators/KDF1BytesGenerator.java | 23 + .../crypto/generators/KDF2BytesGenerator.java | 24 + .../crypto/generators/MGF1BytesGenerator.java | 114 ++ .../NaccacheSternKeyPairGenerator.java | 365 +++++ .../OpenSSLPBEParametersGenerator.java | 131 ++ .../generators/PKCS12ParametersGenerator.java | 221 +++ .../PKCS5S1ParametersGenerator.java | 119 ++ .../PKCS5S2ParametersGenerator.java | 151 ++ .../RSABlindingFactorGenerator.java | 77 + .../generators/RSAKeyPairGenerator.java | 147 ++ .../crypto/io/DigestInputStream.java | 52 + .../crypto/io/DigestOutputStream.java | 43 + .../crypto/io/MacInputStream.java | 52 + .../crypto/io/MacOutputStream.java | 44 + .../crypto/io/SignerInputStream.java | 52 + .../crypto/io/SignerOutputStream.java | 43 + .../crypto/macs/BlockCipherMac.java | 174 ++ .../crypto/macs/CBCBlockCipherMac.java | 229 +++ .../crypto/macs/CFBBlockCipherMac.java | 388 +++++ .../bouncycastle/crypto/macs/CMac.java | 237 +++ .../crypto/macs/GOST28147Mac.java | 298 ++++ .../bouncycastle/crypto/macs/HMac.java | 199 +++ .../crypto/macs/ISO9797Alg3Mac.java | 287 ++++ .../bouncycastle/crypto/macs/OldHMac.java | 138 ++ .../bouncycastle/crypto/macs/VMPCMac.java | 186 +++ .../crypto/modes/AEADBlockCipher.java | 108 ++ .../crypto/modes/CBCBlockCipher.java | 235 +++ .../crypto/modes/CCMBlockCipher.java | 338 ++++ .../crypto/modes/CFBBlockCipher.java | 250 +++ .../crypto/modes/CTSBlockCipher.java | 265 ++++ .../crypto/modes/EAXBlockCipher.java | 304 ++++ .../crypto/modes/GCMBlockCipher.java | 416 +++++ .../crypto/modes/GOFBBlockCipher.java | 226 +++ .../crypto/modes/OFBBlockCipher.java | 179 +++ .../crypto/modes/OpenPGPCFBBlockCipher.java | 312 ++++ .../crypto/modes/PGPCFBBlockCipher.java | 450 ++++++ .../crypto/modes/PaddedBlockCipher.java | 253 +++ .../crypto/modes/SICBlockCipher.java | 119 ++ .../crypto/modes/gcm/BasicGCMMultiplier.java | 41 + .../crypto/modes/gcm/GCMMultiplier.java | 7 + .../crypto/modes/gcm/GCMUtil.java | 85 + .../modes/gcm/Tables64kGCMMultiplier.java | 75 + .../modes/gcm/Tables8kGCMMultiplier.java | 102 ++ .../crypto/paddings/BlockCipherPadding.java | 48 + .../crypto/paddings/ISO10126d2Padding.java | 79 + .../crypto/paddings/ISO7816d4Padding.java | 77 + .../crypto/paddings/PKCS7Padding.java | 76 + .../paddings/PaddedBufferedBlockCipher.java | 298 ++++ .../crypto/paddings/TBCPadding.java | 89 ++ .../crypto/paddings/X923Padding.java | 80 + .../crypto/paddings/ZeroBytePadding.java | 73 + .../crypto/params/AEADParameters.java | 48 + .../crypto/params/AsymmetricKeyParameter.java | 20 + .../crypto/params/CCMParameters.java | 18 + .../crypto/params/DESParameters.java | 107 ++ .../crypto/params/DESedeParameters.java | 57 + .../params/DHKeyGenerationParameters.java | 30 + .../crypto/params/DHKeyParameters.java | 54 + .../crypto/params/DHParameters.java | 188 +++ .../crypto/params/DHPrivateKeyParameters.java | 41 + .../crypto/params/DHPublicKeyParameters.java | 41 + .../crypto/params/DHValidationParameters.java | 50 + .../params/DSAKeyGenerationParameters.java | 25 + .../crypto/params/DSAKeyParameters.java | 21 + .../crypto/params/DSAParameters.java | 74 + .../params/DSAPrivateKeyParameters.java | 23 + .../crypto/params/DSAPublicKeyParameters.java | 23 + .../params/DSAValidationParameters.java | 50 + .../crypto/params/ECDomainParameters.java | 81 + .../params/ECKeyGenerationParameters.java | 25 + .../crypto/params/ECKeyParameters.java | 21 + .../crypto/params/ECPrivateKeyParameters.java | 22 + .../crypto/params/ECPublicKeyParameters.java | 22 + .../ElGamalKeyGenerationParameters.java | 30 + .../crypto/params/ElGamalKeyParameters.java | 47 + .../crypto/params/ElGamalParameters.java | 69 + .../params/ElGamalPrivateKeyParameters.java | 46 + .../params/ElGamalPublicKeyParameters.java | 41 + .../GOST3410KeyGenerationParameters.java | 25 + .../crypto/params/GOST3410KeyParameters.java | 21 + .../crypto/params/GOST3410Parameters.java | 74 + .../params/GOST3410PrivateKeyParameters.java | 23 + .../params/GOST3410PublicKeyParameters.java | 23 + .../params/GOST3410ValidationParameters.java | 84 + .../crypto/params/IESParameters.java | 44 + .../params/IESWithCipherParameters.java | 30 + .../crypto/params/ISO18033KDFParameters.java | 23 + .../crypto/params/KDFParameters.java | 31 + .../crypto/params/KeyParameter.java | 30 + .../crypto/params/MGFParameters.java | 32 + .../crypto/params/MQVPrivateParameters.java | 43 + .../crypto/params/MQVPublicParameters.java | 28 + .../NaccacheSternKeyGenerationParameters.java | 97 ++ .../params/NaccacheSternKeyParameters.java | 53 + .../NaccacheSternPrivateKeyParameters.java | 50 + .../crypto/params/ParametersWithIV.java | 39 + .../crypto/params/ParametersWithRandom.java | 36 + .../crypto/params/ParametersWithSBox.java | 28 + .../crypto/params/ParametersWithSalt.java | 42 + .../crypto/params/RC2Parameters.java | 36 + .../crypto/params/RC5Parameters.java | 35 + .../crypto/params/RSABlindingParameters.java | 35 + .../params/RSAKeyGenerationParameters.java | 48 + .../crypto/params/RSAKeyParameters.java | 31 + .../params/RSAPrivateCrtKeyParameters.java | 67 + .../crypto/prng/DigestRandomGenerator.java | 123 ++ .../crypto/prng/RandomGenerator.java | 38 + .../crypto/prng/ReversedWindowGenerator.java | 111 ++ .../crypto/prng/ThreadedSeedGenerator.java | 95 ++ .../crypto/prng/VMPCRandomGenerator.java | 131 ++ .../crypto/signers/DSADigestSigner.java | 154 ++ .../crypto/signers/DSASigner.java | 138 ++ .../crypto/signers/ECDSASigner.java | 164 ++ .../crypto/signers/ECGOST3410Signer.java | 152 ++ .../crypto/signers/ECNRSigner.java | 182 +++ .../crypto/signers/GOST3410Signer.java | 127 ++ .../crypto/signers/GenericSigner.java | 136 ++ .../crypto/signers/ISO9796d2PSSSigner.java | 608 +++++++ .../crypto/signers/ISO9796d2Signer.java | 487 ++++++ .../crypto/signers/PSSSigner.java | 337 ++++ .../crypto/signers/RSADigestSigner.java | 230 +++ .../crypto/tls/AlwaysValidVerifyer.java | 24 + .../bouncycastle/crypto/tls/ByteQueue.java | 131 ++ .../bouncycastle/crypto/tls/Certificate.java | 77 + .../crypto/tls/CertificateVerifyer.java | 16 + .../bouncycastle/crypto/tls/CombinedHash.java | 68 + .../bouncycastle/crypto/tls/RecordStream.java | 99 ++ .../crypto/tls/TlsBlockCipherCipherSuite.java | 190 +++ .../crypto/tls/TlsCipherSuite.java | 32 + .../crypto/tls/TlsCipherSuiteManager.java | 158 ++ .../bouncycastle/crypto/tls/TlsDSSSigner.java | 14 + .../crypto/tls/TlsInputStream.java | 41 + .../bouncycastle/crypto/tls/TlsMac.java | 78 + .../crypto/tls/TlsNullCipherSuite.java | 33 + .../crypto/tls/TlsOuputStream.java | 45 + .../crypto/tls/TlsProtocolHandler.java | 1407 +++++++++++++++++ .../bouncycastle/crypto/tls/TlsRSASigner.java | 14 + .../crypto/tls/TlsRuntimeException.java | 24 + .../bouncycastle/crypto/tls/TlsUtils.java | 266 ++++ .../bouncycastle/crypto/util/Pack.java | 34 + .../crypto/util/PrivateKeyFactory.java | 190 +++ .../crypto/util/PublicKeyFactory.java | 194 +++ .../bouncycastle/math/ec/ECAlgorithms.java | 93 ++ .../bouncycastle/math/ec/ECConstants.java | 12 + .../bitcoin/bouncycastle/math/ec/ECCurve.java | 660 ++++++++ .../bouncycastle/math/ec/ECFieldElement.java | 1196 ++++++++++++++ .../bouncycastle/math/ec/ECMultiplier.java | 19 + .../bitcoin/bouncycastle/math/ec/ECPoint.java | 594 +++++++ .../bouncycastle/math/ec/FpNafMultiplier.java | 39 + .../bouncycastle/math/ec/IntArray.java | 518 ++++++ .../bouncycastle/math/ec/PreCompInfo.java | 10 + .../math/ec/ReferenceMultiplier.java | 30 + .../math/ec/SimpleBigDecimal.java | 253 +++ .../bitcoin/bouncycastle/math/ec/Tnaf.java | 844 ++++++++++ .../bouncycastle/math/ec/WNafMultiplier.java | 240 +++ .../bouncycastle/math/ec/WNafPreCompInfo.java | 44 + .../math/ec/WTauNafMultiplier.java | 119 ++ .../math/ec/WTauNafPreCompInfo.java | 39 + .../bouncycastle/math/ec/ZTauElement.java | 37 + .../bitcoin/bouncycastle/util/Arrays.java | 214 +++ .../bouncycastle/util/BigIntegers.java | 78 + .../bouncycastle/util/CollectionStore.java | 57 + .../bitcoin/bouncycastle/util/IPAddress.java | 188 +++ .../bitcoin/bouncycastle/util/Selector.java | 9 + .../bitcoin/bouncycastle/util/Store.java | 9 + .../bouncycastle/util/StoreException.java | 18 + .../bouncycastle/util/StreamParser.java | 10 + .../util/StreamParsingException.java | 18 + .../bitcoin/bouncycastle/util/Strings.java | 246 +++ .../bouncycastle/util/encoders/Base64.java | 121 ++ .../util/encoders/Base64Encoder.java | 298 ++++ .../util/encoders/BufferedDecoder.java | 96 ++ .../util/encoders/BufferedEncoder.java | 96 ++ .../bouncycastle/util/encoders/Encoder.java | 17 + .../bouncycastle/util/encoders/Hex.java | 131 ++ .../util/encoders/HexEncoder.java | 172 ++ .../util/encoders/HexTranslator.java | 87 + .../util/encoders/Translator.java | 23 + .../bouncycastle/util/encoders/UrlBase64.java | 129 ++ .../util/encoders/UrlBase64Encoder.java | 25 + .../util/io/StreamOverflowException.java | 12 + .../bitcoin/bouncycastle/util/io/Streams.java | 87 + src/com/google/bitcoin/core/Address.java | 111 ++ .../bitcoin/core/AddressFormatException.java | 28 + .../google/bitcoin/core/AddressMessage.java | 42 + src/com/google/bitcoin/core/Base58.java | 73 + src/com/google/bitcoin/core/Block.java | 313 ++++ src/com/google/bitcoin/core/BlockChain.java | 247 +++ src/com/google/bitcoin/core/ECKey.java | 198 +++ .../google/bitcoin/core/GetBlocksMessage.java | 66 + .../google/bitcoin/core/GetDataMessage.java | 33 + .../google/bitcoin/core/InventoryItem.java | 38 + .../google/bitcoin/core/InventoryMessage.java | 78 + src/com/google/bitcoin/core/Message.java | 142 ++ .../bitcoin/core/NetworkConnection.java | 260 +++ .../bitcoin/core/NetworkParameters.java | 104 ++ src/com/google/bitcoin/core/Peer.java | 355 +++++ src/com/google/bitcoin/core/PeerAddress.java | 90 ++ .../bitcoin/core/ProtocolException.java | 33 + src/com/google/bitcoin/core/Script.java | 397 +++++ .../google/bitcoin/core/ScriptException.java | 29 + src/com/google/bitcoin/core/Transaction.java | 364 +++++ .../google/bitcoin/core/TransactionInput.java | 127 ++ .../bitcoin/core/TransactionOutPoint.java | 92 ++ .../bitcoin/core/TransactionOutput.java | 134 ++ .../google/bitcoin/core/UnknownMessage.java | 35 + src/com/google/bitcoin/core/Utils.java | 214 +++ src/com/google/bitcoin/core/VarInt.java | 83 + .../bitcoin/core/VerificationException.java | 24 + .../google/bitcoin/core/VersionMessage.java | 93 ++ src/com/google/bitcoin/core/Wallet.java | 346 ++++ .../bitcoin/core/WalletEventListener.java | 21 + .../google/bitcoin/examples/PingService.java | 89 ++ .../google/bitcoin/examples/PrivateKeys.java | 67 + .../com/google/bitcoin/core/AddressTest.java | 44 + tests/com/google/bitcoin/core/Base58Test.java | 38 + .../google/bitcoin/core/BlockChainTest.java | 131 ++ tests/com/google/bitcoin/core/BlockTest.java | 99 ++ tests/com/google/bitcoin/core/ECKeyTest.java | 56 + tests/com/google/bitcoin/core/ScriptTest.java | 72 + tests/com/google/bitcoin/core/VarIntTest.java | 43 + tests/com/google/bitcoin/core/WalletTest.java | 141 ++ 858 files changed, 122170 insertions(+) create mode 100644 COPYING create mode 100644 README create mode 100644 TODO create mode 100644 build.xml create mode 100644 docs/allclasses-frame.html create mode 100644 docs/allclasses-noframe.html create mode 100644 docs/com/google/bitcoin/core/Address.html create mode 100644 docs/com/google/bitcoin/core/AddressFormatException.html create mode 100644 docs/com/google/bitcoin/core/AddressMessage.html create mode 100644 docs/com/google/bitcoin/core/Base58.html create mode 100644 docs/com/google/bitcoin/core/Block.html create mode 100644 docs/com/google/bitcoin/core/BlockChain.html create mode 100644 docs/com/google/bitcoin/core/ECKey.html create mode 100644 docs/com/google/bitcoin/core/GetBlocksMessage.html create mode 100644 docs/com/google/bitcoin/core/GetDataMessage.html create mode 100644 docs/com/google/bitcoin/core/InventoryItem.Type.html create mode 100644 docs/com/google/bitcoin/core/InventoryItem.html create mode 100644 docs/com/google/bitcoin/core/InventoryMessage.html create mode 100644 docs/com/google/bitcoin/core/Message.html create mode 100644 docs/com/google/bitcoin/core/NetworkConnection.html create mode 100644 docs/com/google/bitcoin/core/NetworkParameters.html create mode 100644 docs/com/google/bitcoin/core/Peer.html create mode 100644 docs/com/google/bitcoin/core/PeerAddress.html create mode 100644 docs/com/google/bitcoin/core/ProtocolException.html create mode 100644 docs/com/google/bitcoin/core/Script.html create mode 100644 docs/com/google/bitcoin/core/ScriptException.html create mode 100644 docs/com/google/bitcoin/core/Transaction.SigHash.html create mode 100644 docs/com/google/bitcoin/core/Transaction.html create mode 100644 docs/com/google/bitcoin/core/TransactionInput.html create mode 100644 docs/com/google/bitcoin/core/TransactionOutPoint.html create mode 100644 docs/com/google/bitcoin/core/TransactionOutput.html create mode 100644 docs/com/google/bitcoin/core/UnknownMessage.html create mode 100644 docs/com/google/bitcoin/core/Utils.html create mode 100644 docs/com/google/bitcoin/core/VarInt.html create mode 100644 docs/com/google/bitcoin/core/VerificationException.html create mode 100644 docs/com/google/bitcoin/core/VersionMessage.html create mode 100644 docs/com/google/bitcoin/core/Wallet.html create mode 100644 docs/com/google/bitcoin/core/WalletEventListener.html create mode 100644 docs/com/google/bitcoin/core/package-frame.html create mode 100644 docs/com/google/bitcoin/core/package-summary.html create mode 100644 docs/com/google/bitcoin/core/package-tree.html create mode 100644 docs/com/google/bitcoin/examples/PingService.html create mode 100644 docs/com/google/bitcoin/examples/PrivateKeys.html create mode 100644 docs/com/google/bitcoin/examples/package-frame.html create mode 100644 docs/com/google/bitcoin/examples/package-summary.html create mode 100644 docs/com/google/bitcoin/examples/package-tree.html create mode 100644 docs/constant-values.html create mode 100644 docs/deprecated-list.html create mode 100644 docs/help-doc.html create mode 100644 docs/index-all.html create mode 100644 docs/index.html create mode 100644 docs/overview-frame.html create mode 100644 docs/overview-summary.html create mode 100644 docs/overview-tree.html create mode 100644 docs/package-list create mode 100644 docs/resources/inherit.gif create mode 100644 docs/serialized-form.html create mode 100644 docs/stylesheet.css create mode 100644 lib/junit-4.8.2.jar create mode 100644 src/com/google/bitcoin/bouncycastle/LICENSE.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1ApplicationSpecificParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1Choice.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1Encodable.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1EncodableVector.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1Generator.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1InputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1Null.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1Object.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1ObjectParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1OctetString.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1OctetStringParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1OutputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1ParsingException.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1Sequence.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1SequenceParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1Set.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1SetParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1StreamParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1TaggedObject.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ASN1TaggedObjectParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BERApplicationSpecific.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BERApplicationSpecificParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BERConstructedOctetString.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BERConstructedSequence.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BERFactory.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BERGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BERInputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BERNull.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BEROctetStringGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BEROctetStringParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BEROutputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BERSequence.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BERSequenceGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BERSequenceParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BERSet.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BERSetParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BERTaggedObject.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/BERTaggedObjectParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ConstructedOctetStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERApplicationSpecific.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERBMPString.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERBitString.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERBoolean.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERConstructedSequence.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERConstructedSet.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DEREncodable.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DEREncodableVector.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DEREnumerated.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERExternal.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERExternalParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERFactory.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERGeneralString.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERGeneralizedTime.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERIA5String.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERInputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERInteger.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERNull.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERNumericString.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERObject.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERObjectIdentifier.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DEROctetString.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DEROctetStringParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DEROutputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERPrintableString.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERSequence.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERSequenceGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERSequenceParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERSet.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERSetParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERString.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERT61String.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERTaggedObject.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERTags.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERUTCTime.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERUTF8String.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERUniversalString.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERUnknownTag.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DERVisibleString.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/DefiniteLengthInputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/IndefiniteLengthInputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/LazyDERConstructionEnumeration.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/LazyDERSequence.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/LimitedInputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/OIDTokenizer.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/bc/BCObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/CAKeyUpdAnnContent.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/CMPCertificate.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/CRLAnnContent.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/CertConfirmContent.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/CertOrEncCert.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/CertRepMessage.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/CertResponse.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/CertStatus.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/CertifiedKeyPair.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/Challenge.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/ErrorMsgContent.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/GenMsgContent.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/GenRepContent.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/InfoTypeAndValue.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/KeyRecRepContent.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/OOBCertHash.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/PBMParameter.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIBody.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIConfirmContent.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIFailureInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIFreeText.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIHeader.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIMessage.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIMessages.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIStatus.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIStatusInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/POPODecKeyChallContent.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/POPODecKeyRespContent.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/PollRepContent.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/PollReqContent.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/ProtectedPart.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/RevAnnContent.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/RevDetails.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/RevRepContent.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cmp/RevReqContent.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/Attribute.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/AttributeTable.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/AuthEnvelopedData.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/AuthEnvelopedDataParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/AuthenticatedData.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/AuthenticatedDataParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/CMSAttributes.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/CMSObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/CompressedData.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/CompressedDataParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/ContentInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/ContentInfoParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/EncryptedContentInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/EncryptedContentInfoParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/EncryptedData.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/EnvelopedData.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/EnvelopedDataParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/IssuerAndSerialNumber.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/KEKIdentifier.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/KEKRecipientInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/KeyAgreeRecipientIdentifier.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/KeyAgreeRecipientInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/KeyTransRecipientInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/OriginatorIdentifierOrKey.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/OriginatorInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/OriginatorPublicKey.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/OtherKeyAttribute.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/OtherRecipientInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/PasswordRecipientInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientEncryptedKey.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientIdentifier.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientKeyIdentifier.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/SignedData.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/SignedDataParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/SignerIdentifier.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/SignerInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/Time.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cms/ecc/MQVuserKeyingMaterial.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/crmf/AttributeTypeAndValue.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/crmf/CertId.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/crmf/CertReqMessages.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/crmf/CertReqMsg.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/crmf/CertRequest.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/crmf/CertTemplate.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/crmf/Controls.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/crmf/EncryptedValue.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/crmf/OptionalValidity.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/crmf/PKIPublicationInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/crmf/POPOPrivKey.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/crmf/POPOSigningKey.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/crmf/POPOSigningKeyInput.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/crmf/ProofOfPossession.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/crmf/SinglePubInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cryptopro/CryptoProObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cryptopro/ECGOST3410NamedCurves.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cryptopro/ECGOST3410ParamSetParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST28147Parameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST3410NamedParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST3410ParamSetParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST3410PublicKeyAlgParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/eac/EACObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/esf/CommitmentTypeIdentifier.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/esf/CommitmentTypeIndication.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/esf/CommitmentTypeQualifier.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/esf/ESFAttributes.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/esf/OtherHashAlgAndValue.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/esf/SPUserNotice.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/esf/SPuri.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/esf/SigPolicyQualifierInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/esf/SigPolicyQualifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/esf/SignaturePolicyId.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/esf/SignaturePolicyIdentifier.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/esf/SignerAttribute.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/esf/SignerLocation.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ess/ContentHints.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ess/ContentIdentifier.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ess/ESSCertID.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ess/ESSCertIDv2.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ess/OtherCertID.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ess/OtherSigningCertificate.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ess/SigningCertificate.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ess/SigningCertificateV2.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/gnu/GNUObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/iana/IANAObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/icao/DataGroupHash.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/icao/ICAOObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/icao/LDSSecurityObject.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/isismtt/ISISMTTObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/isismtt/ocsp/CertHash.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/isismtt/ocsp/RequestedCertificate.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/AdditionalInformationSyntax.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/AdmissionSyntax.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/Admissions.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/DeclarationOfMajority.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/MonetaryLimit.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/NamingAuthority.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/ProcurationSyntax.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/ProfessionInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/Restriction.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/kisa/KISAObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/microsoft/MicrosoftObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/misc/CAST5CBCParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/misc/IDEACBCPar.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/misc/MiscObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/misc/NetscapeCertType.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/misc/NetscapeRevocationURL.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/misc/VerisignCzagExtension.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/mozilla/PublicKeyAndChallenge.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/nist/NISTNamedCurves.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/nist/NISTObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ntt/NTTObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/BasicOCSPResponse.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/CertID.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/CertStatus.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/CrlID.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPRequest.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPResponse.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPResponseStatus.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/Request.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/ResponderID.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/ResponseBytes.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/ResponseData.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/RevokedInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/ServiceLocator.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/Signature.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/SingleResponse.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/ocsp/TBSRequest.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/oiw/ElGamalParameter.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/oiw/OIWObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/Attribute.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/AuthenticatedSafe.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/CertBag.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/CertificationRequest.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/CertificationRequestInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/ContentInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/DHParameter.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/EncryptedData.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/EncryptedPrivateKeyInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/EncryptionScheme.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/IssuerAndSerialNumber.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/KeyDerivationFunc.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/MacData.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/PBES2Algorithms.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/PBES2Parameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/PBKDF2Params.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/PKCS12PBEParams.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/PKCSObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/Pfx.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/PrivateKeyInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/RC2CBCParameter.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/RSAESOAEPparams.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/RSAPrivateKeyStructure.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/RSASSAPSSparams.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/SafeBag.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/SignedData.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/pkcs/SignerInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/sec/ECPrivateKeyStructure.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/sec/SECNamedCurves.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/sec/SECObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMEAttributes.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapabilities.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapabilitiesAttribute.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapability.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapabilityVector.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMEEncryptionKeyPreferenceAttribute.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/teletrust/TeleTrusTNamedCurves.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/teletrust/TeleTrusTObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/tsp/Accuracy.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/tsp/MessageImprint.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/tsp/TSTInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/tsp/TimeStampReq.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/tsp/TimeStampResp.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/util/ASN1Dump.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/util/DERDump.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/util/Dump.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x500/DirectoryString.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/AccessDescription.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/AlgorithmIdentifier.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/AttCertIssuer.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/AttCertValidityPeriod.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/Attribute.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/AttributeCertificate.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/AttributeCertificateInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/AuthorityInformationAccess.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/AuthorityKeyIdentifier.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/BasicConstraints.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/CRLDistPoint.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/CRLNumber.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/CRLReason.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/CertPolicyId.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/CertificateList.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/CertificatePair.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/CertificatePolicies.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/DSAParameter.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/DigestInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/DisplayText.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/DistributionPoint.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/DistributionPointName.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/ExtendedKeyUsage.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/GeneralName.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/GeneralNames.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/GeneralSubtree.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/Holder.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/IetfAttrSyntax.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/IssuerSerial.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/IssuingDistributionPoint.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/KeyPurposeId.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/KeyUsage.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/NameConstraints.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/NoticeReference.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/ObjectDigestInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyInformation.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyMappings.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyQualifierId.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyQualifierInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/PrivateKeyUsagePeriod.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/RSAPublicKeyStructure.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/ReasonFlags.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/RoleSyntax.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/SubjectDirectoryAttributes.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/SubjectKeyIdentifier.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/TBSCertList.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/TBSCertificateStructure.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/Target.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/TargetInformation.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/Targets.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/Time.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/UserNotice.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/V1TBSCertificateGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/V2AttributeCertificateInfoGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/V2Form.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/V2TBSCertListGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/V3TBSCertificateGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/X509Attributes.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/X509CertificateStructure.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/X509DefaultEntryConverter.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/X509Extension.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/X509Extensions.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/X509ExtensionsGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/X509Name.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/X509NameEntryConverter.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/X509NameTokenizer.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/X509ObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/BiometricData.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/ETSIQCObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/Iso4217CurrencyCode.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/MonetaryValue.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/QCStatement.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/RFC3739QCObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/SemanticsInformation.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/TypeOfBiometricData.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/sigi/NameOrPseudonym.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/sigi/PersonalData.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x509/sigi/SigIObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x9/KeySpecificInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x9/OtherInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x9/X962NamedCurves.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x9/X962Parameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x9/X9Curve.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x9/X9ECParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x9/X9ECParametersHolder.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x9/X9ECPoint.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x9/X9FieldElement.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x9/X9FieldID.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x9/X9IntegerConverter.java create mode 100644 src/com/google/bitcoin/bouncycastle/asn1/x9/X9ObjectIdentifiers.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/ArmoredInputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/ArmoredOutputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/BCPGInputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/BCPGKey.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/BCPGObject.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/BCPGOutputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/CRC24.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/CompressedDataPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/CompressionAlgorithmTags.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/ContainedPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/DSAPublicBCPGKey.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/DSASecretBCPGKey.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/ElGamalPublicBCPGKey.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/ElGamalSecretBCPGKey.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/ExperimentalPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/HashAlgorithmTags.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/InputStreamPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/LiteralDataPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/MPInteger.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/MarkerPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/ModDetectionCodePacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/OnePassSignaturePacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/OutputStreamPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/Packet.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/PacketTags.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/PublicKeyAlgorithmTags.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/PublicKeyEncSessionPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/PublicKeyPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/PublicSubkeyPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/RSAPublicBCPGKey.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/RSASecretBCPGKey.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/S2K.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/SecretKeyPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/SecretSubkeyPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/SignaturePacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/SignatureSubpacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/SignatureSubpacketInputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/SignatureSubpacketTags.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/SymmetricEncDataPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/SymmetricEncIntegrityPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/SymmetricKeyAlgorithmTags.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/TrustPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/UserAttributePacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/UserAttributeSubpacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/UserAttributeSubpacketInputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/UserAttributeSubpacketTags.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/UserIDPacket.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/attr/ImageAttribute.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/sig/EmbeddedSignature.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/sig/Exportable.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/sig/IssuerKeyID.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/sig/KeyExpirationTime.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/sig/KeyFlags.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/sig/NotationData.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/sig/PreferredAlgorithms.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/sig/PrimaryUserID.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/sig/Revocable.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/sig/SignatureCreationTime.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/sig/SignatureExpirationTime.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/sig/SignerUserID.java create mode 100644 src/com/google/bitcoin/bouncycastle/bcpg/sig/TrustSignature.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/AsymmetricBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/AsymmetricCipherKeyPair.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/AsymmetricCipherKeyPairGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/BasicAgreement.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/BlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/BufferedAsymmetricBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/BufferedBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/CipherKeyGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/CipherParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/CryptoException.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/DSA.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/DataLengthException.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/DerivationFunction.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/DerivationParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/Digest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/ExtendedDigest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/InvalidCipherTextException.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/KeyGenerationParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/Mac.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/MaxBytesExceededException.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/PBEParametersGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/RuntimeCryptoException.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/Signer.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/SignerWithRecovery.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/StreamBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/StreamCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/Wrapper.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/agreement/DHAgreement.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/agreement/DHBasicAgreement.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/agreement/ECDHBasicAgreement.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/agreement/ECDHCBasicAgreement.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/agreement/ECMQVBasicAgreement.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/agreement/kdf/DHKDFParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/agreement/kdf/DHKEKGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/agreement/kdf/ECDHKEKGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6Client.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6Server.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6Util.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6VerifierGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/GOST3411Digest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/GeneralDigest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/LongDigest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/MD2Digest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/MD4Digest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/MD5Digest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD128Digest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD160Digest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD256Digest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD320Digest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/SHA1Digest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/SHA224Digest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/SHA256Digest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/SHA384Digest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/SHA512Digest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/ShortenedDigest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/TigerDigest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/digests/WhirlpoolDigest.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/encodings/ISO9796d1Encoding.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/encodings/OAEPEncoding.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/encodings/PKCS1Encoding.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/AESEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/AESFastEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/AESLightEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/AESWrapEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/BlowfishEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/CAST5Engine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/CAST6Engine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/CamelliaEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/CamelliaLightEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/CamelliaWrapEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/DESEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/DESedeEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/DESedeWrapEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/ElGamalEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/GOST28147Engine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/Grain128Engine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/Grainv1Engine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/HC128Engine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/HC256Engine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/IDEAEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/IESEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/ISAACEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/NaccacheSternEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/NoekeonEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/NullEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/RC2Engine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/RC2WrapEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/RC4Engine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/RC532Engine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/RC564Engine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/RC6Engine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/RFC3211WrapEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/RFC3394WrapEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/RSABlindedEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/RSABlindingEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/RSACoreEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/RSAEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/RijndaelEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/SEEDEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/SEEDWrapEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/Salsa20Engine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/SerpentEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/SkipjackEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/TEAEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/TwofishEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/VMPCEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/VMPCKSA3Engine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/engines/XTEAEngine.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/examples/DESExample.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/BaseKDFBytesGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/DESKeyGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/DESedeKeyGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/DHBasicKeyPairGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/DHKeyGeneratorHelper.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/DHKeyPairGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/DHParametersGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/DHParametersHelper.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/DSAKeyPairGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/DSAParametersGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/ECKeyPairGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/ElGamalKeyPairGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/ElGamalParametersGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/GOST3410KeyPairGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/GOST3410ParametersGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/KDF1BytesGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/KDF2BytesGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/MGF1BytesGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/NaccacheSternKeyPairGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/OpenSSLPBEParametersGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/PKCS12ParametersGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/PKCS5S1ParametersGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/RSABlindingFactorGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/generators/RSAKeyPairGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/io/DigestInputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/io/DigestOutputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/io/MacInputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/io/MacOutputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/io/SignerInputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/io/SignerOutputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/macs/BlockCipherMac.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/macs/CBCBlockCipherMac.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/macs/CFBBlockCipherMac.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/macs/CMac.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/macs/GOST28147Mac.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/macs/HMac.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/macs/ISO9797Alg3Mac.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/macs/OldHMac.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/macs/VMPCMac.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/AEADBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/CBCBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/CCMBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/CFBBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/CTSBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/EAXBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/GCMBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/GOFBBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/OFBBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/OpenPGPCFBBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/PGPCFBBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/PaddedBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/SICBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/BasicGCMMultiplier.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/GCMMultiplier.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/GCMUtil.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/Tables64kGCMMultiplier.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/paddings/BlockCipherPadding.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/paddings/ISO10126d2Padding.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/paddings/ISO7816d4Padding.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/paddings/PKCS7Padding.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/paddings/TBCPadding.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/paddings/X923Padding.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/paddings/ZeroBytePadding.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/AEADParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/AsymmetricKeyParameter.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/CCMParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/DESParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/DESedeParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/DHKeyGenerationParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/DHKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/DHParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/DHPrivateKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/DHPublicKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/DHValidationParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/DSAKeyGenerationParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/DSAKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/DSAParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/DSAPrivateKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/DSAPublicKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/DSAValidationParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/ECDomainParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/ECKeyGenerationParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/ECKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/ECPrivateKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/ECPublicKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalKeyGenerationParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalPrivateKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalPublicKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410KeyGenerationParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410KeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410Parameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410PrivateKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410PublicKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410ValidationParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/IESParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/IESWithCipherParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/ISO18033KDFParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/KDFParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/KeyParameter.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/MGFParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/MQVPrivateParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/MQVPublicParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/NaccacheSternKeyGenerationParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/NaccacheSternKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/NaccacheSternPrivateKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithIV.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithRandom.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithSBox.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithSalt.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/RC2Parameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/RC5Parameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/RSABlindingParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/RSAKeyGenerationParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/RSAKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/params/RSAPrivateCrtKeyParameters.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/prng/DigestRandomGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/prng/RandomGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/prng/ReversedWindowGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/prng/ThreadedSeedGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/prng/VMPCRandomGenerator.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/signers/DSADigestSigner.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/signers/DSASigner.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/signers/ECDSASigner.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/signers/ECGOST3410Signer.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/signers/ECNRSigner.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/signers/GOST3410Signer.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/signers/GenericSigner.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/signers/ISO9796d2PSSSigner.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/signers/ISO9796d2Signer.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/signers/PSSSigner.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/signers/RSADigestSigner.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/AlwaysValidVerifyer.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/ByteQueue.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/Certificate.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/CertificateVerifyer.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/CombinedHash.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/RecordStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/TlsBlockCipherCipherSuite.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/TlsCipherSuite.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/TlsCipherSuiteManager.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/TlsDSSSigner.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/TlsInputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/TlsMac.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/TlsNullCipherSuite.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/TlsOuputStream.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/TlsProtocolHandler.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/TlsRSASigner.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/TlsRuntimeException.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/tls/TlsUtils.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/util/Pack.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/util/PrivateKeyFactory.java create mode 100644 src/com/google/bitcoin/bouncycastle/crypto/util/PublicKeyFactory.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/ECAlgorithms.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/ECConstants.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/ECCurve.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/ECFieldElement.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/ECMultiplier.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/ECPoint.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/FpNafMultiplier.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/IntArray.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/PreCompInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/ReferenceMultiplier.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/SimpleBigDecimal.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/Tnaf.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/WNafMultiplier.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/WNafPreCompInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/WTauNafMultiplier.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/WTauNafPreCompInfo.java create mode 100644 src/com/google/bitcoin/bouncycastle/math/ec/ZTauElement.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/Arrays.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/BigIntegers.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/CollectionStore.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/IPAddress.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/Selector.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/Store.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/StoreException.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/StreamParser.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/StreamParsingException.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/Strings.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/encoders/Base64.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/encoders/Base64Encoder.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/encoders/BufferedDecoder.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/encoders/BufferedEncoder.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/encoders/Encoder.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/encoders/Hex.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/encoders/HexEncoder.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/encoders/HexTranslator.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/encoders/Translator.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/encoders/UrlBase64.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/encoders/UrlBase64Encoder.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/io/StreamOverflowException.java create mode 100644 src/com/google/bitcoin/bouncycastle/util/io/Streams.java create mode 100644 src/com/google/bitcoin/core/Address.java create mode 100644 src/com/google/bitcoin/core/AddressFormatException.java create mode 100644 src/com/google/bitcoin/core/AddressMessage.java create mode 100644 src/com/google/bitcoin/core/Base58.java create mode 100644 src/com/google/bitcoin/core/Block.java create mode 100644 src/com/google/bitcoin/core/BlockChain.java create mode 100644 src/com/google/bitcoin/core/ECKey.java create mode 100644 src/com/google/bitcoin/core/GetBlocksMessage.java create mode 100644 src/com/google/bitcoin/core/GetDataMessage.java create mode 100644 src/com/google/bitcoin/core/InventoryItem.java create mode 100644 src/com/google/bitcoin/core/InventoryMessage.java create mode 100644 src/com/google/bitcoin/core/Message.java create mode 100644 src/com/google/bitcoin/core/NetworkConnection.java create mode 100644 src/com/google/bitcoin/core/NetworkParameters.java create mode 100644 src/com/google/bitcoin/core/Peer.java create mode 100644 src/com/google/bitcoin/core/PeerAddress.java create mode 100644 src/com/google/bitcoin/core/ProtocolException.java create mode 100644 src/com/google/bitcoin/core/Script.java create mode 100644 src/com/google/bitcoin/core/ScriptException.java create mode 100644 src/com/google/bitcoin/core/Transaction.java create mode 100644 src/com/google/bitcoin/core/TransactionInput.java create mode 100644 src/com/google/bitcoin/core/TransactionOutPoint.java create mode 100644 src/com/google/bitcoin/core/TransactionOutput.java create mode 100644 src/com/google/bitcoin/core/UnknownMessage.java create mode 100644 src/com/google/bitcoin/core/Utils.java create mode 100644 src/com/google/bitcoin/core/VarInt.java create mode 100644 src/com/google/bitcoin/core/VerificationException.java create mode 100644 src/com/google/bitcoin/core/VersionMessage.java create mode 100644 src/com/google/bitcoin/core/Wallet.java create mode 100644 src/com/google/bitcoin/core/WalletEventListener.java create mode 100644 src/com/google/bitcoin/examples/PingService.java create mode 100644 src/com/google/bitcoin/examples/PrivateKeys.java create mode 100644 tests/com/google/bitcoin/core/AddressTest.java create mode 100644 tests/com/google/bitcoin/core/Base58Test.java create mode 100644 tests/com/google/bitcoin/core/BlockChainTest.java create mode 100644 tests/com/google/bitcoin/core/BlockTest.java create mode 100644 tests/com/google/bitcoin/core/ECKeyTest.java create mode 100644 tests/com/google/bitcoin/core/ScriptTest.java create mode 100644 tests/com/google/bitcoin/core/VarIntTest.java create mode 100644 tests/com/google/bitcoin/core/WalletTest.java diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..7a4a3ea24 --- /dev/null +++ b/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. \ No newline at end of file diff --git a/README b/README new file mode 100644 index 000000000..be977820e --- /dev/null +++ b/README @@ -0,0 +1,12 @@ +To get started, ensure you have the latest JDK installed and then just run "ant". + +- A JAR will be placed in dist/ +- JavaDocs will be placed in docs/ +- Unit tests will be run + +Now ensure you're running a BitCoin node locally and run the example app: + + cd out + java com.google.bitcoin.examples.PingService + +It will print a BitCoin address. If you send coins to it, you should get them back a few minutes later when a block is solved. \ No newline at end of file diff --git a/TODO b/TODO new file mode 100644 index 000000000..af77d7310 --- /dev/null +++ b/TODO @@ -0,0 +1,23 @@ +Here is a brief list of things that still need to be done. + +More unit tests: + - Implement more unit tests for block chain functionality + - Create a MockNetworkConnection and use it to test the Peer. + +Protocol: + - Properly support multiple chains + - Start up faster + - Store a block locator in the wallet + - Parse the stored headers at startup, then download the rest + - Provide a seed node list in the NetworkParameters. + - Implement tx fees. + - Support import of private keys and then extracting its transactions from the block chain. + - Longer term potentially add a getmerklebranch protocol command so we can check 0-confirmation transactions. + +Examples/documentation: + - Implement a simple Swing GUI payment app. + +Cleanup: + - Find a way to avoid some horrid hacks when shutting down the network connection. + - Implement a BitCoin class that encapsulates a BigInteger and formatting. + - Make NetworkParameters use subclasses instead of static methods to construct. diff --git a/build.xml b/build.xml new file mode 100644 index 000000000..699ab285e --- /dev/null +++ b/build.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/allclasses-frame.html b/docs/allclasses-frame.html new file mode 100644 index 000000000..dd6c60aa8 --- /dev/null +++ b/docs/allclasses-frame.html @@ -0,0 +1,97 @@ + + + + + + +All Classes + + + + + + + + + + + +All Classes +
+ + + + + +
Address +
+AddressFormatException +
+AddressMessage +
+Base58 +
+Block +
+BlockChain +
+ECKey +
+GetBlocksMessage +
+GetDataMessage +
+InventoryItem +
+InventoryItem.Type +
+InventoryMessage +
+Message +
+NetworkConnection +
+NetworkParameters +
+Peer +
+PeerAddress +
+PingService +
+PrivateKeys +
+ProtocolException +
+Script +
+ScriptException +
+Transaction +
+Transaction.SigHash +
+TransactionInput +
+TransactionOutPoint +
+TransactionOutput +
+UnknownMessage +
+Utils +
+VarInt +
+VerificationException +
+VersionMessage +
+Wallet +
+WalletEventListener +
+
+ + + diff --git a/docs/allclasses-noframe.html b/docs/allclasses-noframe.html new file mode 100644 index 000000000..2af5de498 --- /dev/null +++ b/docs/allclasses-noframe.html @@ -0,0 +1,97 @@ + + + + + + +All Classes + + + + + + + + + + + +All Classes +
+ + + + + +
Address +
+AddressFormatException +
+AddressMessage +
+Base58 +
+Block +
+BlockChain +
+ECKey +
+GetBlocksMessage +
+GetDataMessage +
+InventoryItem +
+InventoryItem.Type +
+InventoryMessage +
+Message +
+NetworkConnection +
+NetworkParameters +
+Peer +
+PeerAddress +
+PingService +
+PrivateKeys +
+ProtocolException +
+Script +
+ScriptException +
+Transaction +
+Transaction.SigHash +
+TransactionInput +
+TransactionOutPoint +
+TransactionOutput +
+UnknownMessage +
+Utils +
+VarInt +
+VerificationException +
+VersionMessage +
+Wallet +
+WalletEventListener +
+
+ + + diff --git a/docs/com/google/bitcoin/core/Address.html b/docs/com/google/bitcoin/core/Address.html new file mode 100644 index 000000000..338203cb8 --- /dev/null +++ b/docs/com/google/bitcoin/core/Address.html @@ -0,0 +1,366 @@ + + + + + + +Address + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class Address

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Address
+
+
+
+
public class Address
extends java.lang.Object
+ + +

+A BitCoin address is fundamentally derived from an elliptic curve public key and a set of network parameters. + It has several possible representations:

+ +

    +
  1. The raw public key bytes themselves. +
  2. RIPEMD160 hash of the public key bytes. +
  3. A base58 encoded "human form" that includes a version and check code, to guard against typos. +

+ + One may question whether the base58 form is really an improvement over the hash160 form, given + they are both very unfriendly for typists. More useful representations might include qrcodes + and identicons.

+ + Note that an address is specific to a network because the first byte is a discriminator value. +

+ +

+


+ +

+ + + + + + + + + + + + + + +
+Constructor Summary
Address(NetworkParameters params, + byte[] hash160) + +
+          Construct an address from parameters and the hash160 form.
Address(NetworkParameters params, + java.lang.String address) + +
+          Construct an address from parameters and the standard "human readable" form.
+  + + + + + + + + + + + + + + + + + + + + + + + +
+Method Summary
+ booleanequals(java.lang.Object o) + +
+           
+ byte[]getHash160() + +
+          The (big endian) 20 byte hash that is the core of a BitCoin address.
+ inthashCode() + +
+           
+ java.lang.StringtoString() + +
+           
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, finalize, getClass, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+Address

+
+public Address(NetworkParameters params,
+               byte[] hash160)
+
+
Construct an address from parameters and the hash160 form. Example:

+ +

new Address(NetworkParameters.prodNet(), Hex.decode("4a22c3c4cbb31e4d03b15550636762bda0baf85a"));
+

+

+
+ +

+Address

+
+public Address(NetworkParameters params,
+               java.lang.String address)
+        throws AddressFormatException
+
+
Construct an address from parameters and the standard "human readable" form. Example:

+ +

new Address(NetworkParameters.prodNet(), "17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL");
+

+

+ +
Throws: +
AddressFormatException
+
+ + + + + + + + +
+Method Detail
+ +

+getHash160

+
+public byte[] getHash160()
+
+
The (big endian) 20 byte hash that is the core of a BitCoin address. +

+

+
+
+
+
+ +

+equals

+
+public boolean equals(java.lang.Object o)
+
+
+
Overrides:
equals in class java.lang.Object
+
+
+
+
+
+
+ +

+hashCode

+
+public int hashCode()
+
+
+
Overrides:
hashCode in class java.lang.Object
+
+
+
+
+
+
+ +

+toString

+
+public java.lang.String toString()
+
+
+
Overrides:
toString in class java.lang.Object
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/AddressFormatException.html b/docs/com/google/bitcoin/core/AddressFormatException.html new file mode 100644 index 000000000..5fce7d7d2 --- /dev/null +++ b/docs/com/google/bitcoin/core/AddressFormatException.html @@ -0,0 +1,252 @@ + + + + + + +AddressFormatException + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class AddressFormatException

+
+java.lang.Object
+  extended by java.lang.Throwable
+      extended by java.lang.Exception
+          extended by com.google.bitcoin.core.AddressFormatException
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class AddressFormatException
extends java.lang.Exception
+ + +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + + + + + + + + +
+Constructor Summary
AddressFormatException() + +
+           
AddressFormatException(java.lang.String message) + +
+           
+  + + + + + + + +
+Method Summary
+ + + + + + + +
Methods inherited from class java.lang.Throwable
fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+AddressFormatException

+
+public AddressFormatException()
+
+
+
+ +

+AddressFormatException

+
+public AddressFormatException(java.lang.String message)
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/AddressMessage.html b/docs/com/google/bitcoin/core/AddressMessage.html new file mode 100644 index 000000000..e10c528cf --- /dev/null +++ b/docs/com/google/bitcoin/core/AddressMessage.html @@ -0,0 +1,253 @@ + + + + + + +AddressMessage + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class AddressMessage

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Message
+      extended by com.google.bitcoin.core.AddressMessage
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class AddressMessage
extends Message
+ + +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + +
+Field Summary
+ + + + + + + +
Fields inherited from class com.google.bitcoin.core.Message
bytes, cursor, MAX_SIZE, offset, params
+  + + + + + + + + + + + +
+Method Summary
+ java.lang.StringtoString() + +
+           
+ + + + + + + +
Methods inherited from class com.google.bitcoin.core.Message
bitcoinSerialize
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Method Detail
+ +

+toString

+
+public java.lang.String toString()
+
+
+
Overrides:
toString in class java.lang.Object
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/Base58.html b/docs/com/google/bitcoin/core/Base58.html new file mode 100644 index 000000000..507927bc8 --- /dev/null +++ b/docs/com/google/bitcoin/core/Base58.html @@ -0,0 +1,303 @@ + + + + + + +Base58 + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class Base58

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Base58
+
+
+
+
public class Base58
extends java.lang.Object
+ + +

+A custom form of base58 is used to encode BitCoin addresses. Note that this is not the same base58 as used by + Flickr, which you may see reference to around the internet.

+ + Satoshi says: why base-58 instead of standard base-64 encoding?

+ +

+

+ +

+


+ +

+ + + + + + + + + + + +
+Constructor Summary
Base58() + +
+           
+  + + + + + + + + + + + + + + + + + + + +
+Method Summary
+static byte[]decode(java.lang.String input) + +
+           
+static java.math.BigIntegerdecodeToBigInteger(java.lang.String input) + +
+           
+static java.lang.Stringencode(byte[] input) + +
+           
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+Base58

+
+public Base58()
+
+
+ + + + + + + + +
+Method Detail
+ +

+encode

+
+public static java.lang.String encode(byte[] input)
+
+
+
+
+
+
+ +

+decode

+
+public static byte[] decode(java.lang.String input)
+
+
+
+
+
+
+ +

+decodeToBigInteger

+
+public static java.math.BigInteger decodeToBigInteger(java.lang.String input)
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/Block.html b/docs/com/google/bitcoin/core/Block.html new file mode 100644 index 000000000..1e7c4ef8a --- /dev/null +++ b/docs/com/google/bitcoin/core/Block.html @@ -0,0 +1,492 @@ + + + + + + +Block + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class Block

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Message
+      extended by com.google.bitcoin.core.Block
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class Block
extends Message
+ + +

+A block is the foundation of the BitCoin system. It records a set of Transactions together with + some data that links it into a place in the global block chain, and proves that a difficult calculation was done + over its contents. See the BitCoin technical paper for more detail on blocks. + + To get a block, you can either build one from the raw bytes you can get from another implementation, or more likely + you grab it from a downloaded BlockChain. +

+ +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + + + + + +
+Field Summary
+static longALLOWED_TIME_DRIFT + +
+           
+ + + + + + + +
Fields inherited from class com.google.bitcoin.core.Message
bytes, cursor, MAX_SIZE, offset, params
+  + + + + + + + + + + +
+Constructor Summary
Block(NetworkParameters params, + byte[] payloadBytes) + +
+          Constructs a block object from the BitCoin wire format.
+  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Method Summary
+ voidaddTransaction(Transaction t) + +
+          Adds a transaction to this block.
+ booleanequals(java.lang.Object o) + +
+           
+ byte[]getHash() + +
+          Returns the hash of the block (which for a valid, solved block should be below the target).
+ java.lang.StringgetHashAsString() + +
+          Returns the hash of the block (which for a valid, solved block should be below the target) in the form seen + on the block explorer.
+ byte[]getMerkleRoot() + +
+          Returns the merkle root in big endian form, calculating it from transactions if necessary.
+ inthashCode() + +
+           
+ java.lang.StringtoString() + +
+          Returns a multi-line string containing a description of the contents of the block.
+ voidverify() + +
+          Checks the block data to ensure it follows the rules laid out in the network parameters.
+ + + + + + + +
Methods inherited from class com.google.bitcoin.core.Message
bitcoinSerialize
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, finalize, getClass, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Field Detail
+ +

+ALLOWED_TIME_DRIFT

+
+public static final long ALLOWED_TIME_DRIFT
+
+
+
See Also:
Constant Field Values
+
+ + + + + + + + +
+Constructor Detail
+ +

+Block

+
+public Block(NetworkParameters params,
+             byte[] payloadBytes)
+      throws ProtocolException
+
+
Constructs a block object from the BitCoin wire format. +

+

+ +
Throws: +
ProtocolException
+
+ + + + + + + + +
+Method Detail
+ +

+getHashAsString

+
+public java.lang.String getHashAsString()
+
+
Returns the hash of the block (which for a valid, solved block should be below the target) in the form seen + on the block explorer. If you call this on block 1 in the production chain, you will get + "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048". +

+

+
+
+
+
+ +

+getHash

+
+public byte[] getHash()
+
+
Returns the hash of the block (which for a valid, solved block should be below the target). Big endian. +

+

+
+
+
+
+ +

+toString

+
+public java.lang.String toString()
+
+
Returns a multi-line string containing a description of the contents of the block. Use for debugging purposes + only. +

+

+
Overrides:
toString in class java.lang.Object
+
+
+
+
+
+
+ +

+verify

+
+public void verify()
+            throws VerificationException
+
+
Checks the block data to ensure it follows the rules laid out in the network parameters. Specifically, throws + an exception if the proof of work is invalid, if the timestamp is too far from what it should be, or if the + transactions don't hash to the value in the merkle root field. This is not everything that is required + for a block to be valid, only what is checkable independent of the chain. +

+

+ +
Throws: +
VerificationException
+
+
+
+ +

+equals

+
+public boolean equals(java.lang.Object o)
+
+
+
Overrides:
equals in class java.lang.Object
+
+
+
+
+
+
+ +

+hashCode

+
+public int hashCode()
+
+
+
Overrides:
hashCode in class java.lang.Object
+
+
+
+
+
+
+ +

+getMerkleRoot

+
+public byte[] getMerkleRoot()
+
+
Returns the merkle root in big endian form, calculating it from transactions if necessary. +

+

+
+
+
+
+ +

+addTransaction

+
+public void addTransaction(Transaction t)
+
+
Adds a transaction to this block. +

+

+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/BlockChain.html b/docs/com/google/bitcoin/core/BlockChain.html new file mode 100644 index 000000000..36bf170f9 --- /dev/null +++ b/docs/com/google/bitcoin/core/BlockChain.html @@ -0,0 +1,328 @@ + + + + + + +BlockChain + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class BlockChain

+
+java.lang.Object
+  extended by com.google.bitcoin.core.BlockChain
+
+
+
+
public class BlockChain
extends java.lang.Object
+ + +

+A BlockChain holds a series of Block objects, links them together, and knows how to verify that the + chain follows the rules of the NetworkParameters for this chain.

+ + A BlockChain requires a Wallet to receive transactions that it finds during the initial download. However, + if you don't care about this, you can just pass in an empty wallet and nothing bad will happen.

+ + A newly constructed BlockChain is empty. To fill it up, use a Peer object to download the chain from the + network.

+ + Notes

+ + The 'chain' can actually be a tree although in normal operation it can be thought of as a simple list. In such a + situation there are multiple stories of the economy competing to become the one true consensus. This can happen + naturally when two miners solve a block within a few seconds of each other, or it can happen when the chain is + under attack.

+ + A reference to the head block of every chain is stored. If you can reach the genesis block by repeatedly walking + through the prevBlock pointers, then we say this is a full chain. If you cannot reach the genesis block we say it is + an orphan chain.

+ + Orphan chains can occur when blocks are solved and received during the initial block chain download, + or if we connect to a peer that doesn't send us blocks in order. +

+ +

+


+ +

+ + + + + + + + + + + +
+Constructor Summary
BlockChain(NetworkParameters params, + Wallet wallet) + +
+           
+  + + + + + + + + + + + + + + + + + + + +
+Method Summary
+ booleanadd(Block block) + +
+          Processes a received block and tries to add it to the chain.
+ BlockgetTopBlock() + +
+          Returns the highest known block or null if the chain is empty (top block is genesis).
+ BlockgetUnconnectedBlock() + +
+          Returns the most recent unconnected block or null if there are none.
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+BlockChain

+
+public BlockChain(NetworkParameters params,
+                  Wallet wallet)
+
+
+ + + + + + + + +
+Method Detail
+ +

+add

+
+public boolean add(Block block)
+            throws VerificationException,
+                   ScriptException
+
+
Processes a received block and tries to add it to the chain. If there's something wrong with the block an + exception is thrown. If the block is OK but cannot be connected to the chain at this time, returns false. + If the block can be connected to the chain, returns true. +

+

+ +
Throws: +
VerificationException +
ScriptException
+
+
+
+ +

+getTopBlock

+
+public Block getTopBlock()
+
+
Returns the highest known block or null if the chain is empty (top block is genesis). +

+

+
+
+
+
+ +

+getUnconnectedBlock

+
+public Block getUnconnectedBlock()
+
+
Returns the most recent unconnected block or null if there are none. This will all have to change. +

+

+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/ECKey.html b/docs/com/google/bitcoin/core/ECKey.html new file mode 100644 index 000000000..45ab97dc7 --- /dev/null +++ b/docs/com/google/bitcoin/core/ECKey.html @@ -0,0 +1,457 @@ + + + + + + +ECKey + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class ECKey

+
+java.lang.Object
+  extended by com.google.bitcoin.core.ECKey
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class ECKey
extends java.lang.Object
implements java.io.Serializable
+ + +

+Represents either an elliptic curve keypair that we own and can use for signing transactions. Currently, + Bouncy Castle is used. In future this may become an interface with multiple implementations using different crypto + libraries. The class also provides a static method that can verify a signature with just the public key.

+

+ +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + + + + + + + + +
+Constructor Summary
ECKey() + +
+          Generates an entirely new keypair.
ECKey(java.math.BigInteger privKey) + +
+          Creates an ECKey given only the private key.
+  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Method Summary
+static ECKeyfromASN1(byte[] asn1privkey) + +
+          Construct an ECKey from an ASN.1 encoded private key.
+ byte[]getPubKey() + +
+          Gets the raw public key value.
+ byte[]getPubKeyHash() + +
+          Gets the hash160 form of the public key (as seen in addresses).
+ byte[]sign(byte[] input) + +
+          Calcuates an ECDSA signature in DER format for the given input hash.
+ AddresstoAddress(NetworkParameters params) + +
+           
+ java.lang.StringtoString() + +
+           
+ booleanverify(byte[] data, + byte[] signature) + +
+          Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key.
+static booleanverify(byte[] data, + byte[] signature, + byte[] pub) + +
+          Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key.
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+ECKey

+
+public ECKey()
+
+
Generates an entirely new keypair. +

+

+
+ +

+ECKey

+
+public ECKey(java.math.BigInteger privKey)
+
+
Creates an ECKey given only the private key. This works because EC public keys are derivable from their + private keys by doing a multiply with the generator value. +

+

+ + + + + + + + +
+Method Detail
+ +

+fromASN1

+
+public static ECKey fromASN1(byte[] asn1privkey)
+
+
Construct an ECKey from an ASN.1 encoded private key. These are produced by OpenSSL and stored by the BitCoin + reference implementation in its wallet. +

+

+
+
+
+
+
+
+
+ +

+getPubKeyHash

+
+public byte[] getPubKeyHash()
+
+
Gets the hash160 form of the public key (as seen in addresses). +

+

+
+
+
+
+
+
+
+ +

+getPubKey

+
+public byte[] getPubKey()
+
+
Gets the raw public key value. +

+

+
+
+
+
+
+
+
+ +

+toString

+
+public java.lang.String toString()
+
+
+
Overrides:
toString in class java.lang.Object
+
+
+
+
+
+
+ +

+toAddress

+
+public Address toAddress(NetworkParameters params)
+
+
+
+
+
+
+
+
+
+ +

+sign

+
+public byte[] sign(byte[] input)
+
+
Calcuates an ECDSA signature in DER format for the given input hash. Note that the input is expected to be + 32 bytes long. +

+

+
+
+
+
+
+
+
+ +

+verify

+
+public static boolean verify(byte[] data,
+                             byte[] signature,
+                             byte[] pub)
+
+
Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. +

+

+
+
+
+
Parameters:
data - Hash of the data to verify.
signature - ASN.1 encoded signature.
pub - The public key bytes to use.
+
+
+
+ +

+verify

+
+public boolean verify(byte[] data,
+                      byte[] signature)
+
+
Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. +

+

+
+
+
+
Parameters:
data - Hash of the data to verify.
signature - ASN.1 encoded signature.
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/GetBlocksMessage.html b/docs/com/google/bitcoin/core/GetBlocksMessage.html new file mode 100644 index 000000000..8c39b0ecc --- /dev/null +++ b/docs/com/google/bitcoin/core/GetBlocksMessage.html @@ -0,0 +1,325 @@ + + + + + + +GetBlocksMessage + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class GetBlocksMessage

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Message
+      extended by com.google.bitcoin.core.GetBlocksMessage
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class GetBlocksMessage
extends Message
+ + +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + +
+Field Summary
+ + + + + + + +
Fields inherited from class com.google.bitcoin.core.Message
bytes, cursor, MAX_SIZE, offset, params
+  + + + + + + + + + + +
+Constructor Summary
GetBlocksMessage(NetworkParameters params, + java.util.List<byte[]> locator, + byte[] stopHash) + +
+           
+  + + + + + + + + + + + + + + + + + + + +
+Method Summary
+ byte[]bitcoinSerialize() + +
+           
+ voidparse() + +
+           
+ java.lang.StringtoString() + +
+           
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+GetBlocksMessage

+
+public GetBlocksMessage(NetworkParameters params,
+                        java.util.List<byte[]> locator,
+                        byte[] stopHash)
+
+
+ + + + + + + + +
+Method Detail
+ +

+parse

+
+public void parse()
+
+
+
+
+
+
+
+
+
+ +

+toString

+
+public java.lang.String toString()
+
+
+
Overrides:
toString in class java.lang.Object
+
+
+
+
+
+
+ +

+bitcoinSerialize

+
+public byte[] bitcoinSerialize()
+
+
+
Overrides:
bitcoinSerialize in class Message
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/GetDataMessage.html b/docs/com/google/bitcoin/core/GetDataMessage.html new file mode 100644 index 000000000..2ea8ef6df --- /dev/null +++ b/docs/com/google/bitcoin/core/GetDataMessage.html @@ -0,0 +1,309 @@ + + + + + + +GetDataMessage + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class GetDataMessage

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Message
+      extended by com.google.bitcoin.core.GetDataMessage
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class GetDataMessage
extends Message
+ + +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + +
+Field Summary
+ + + + + + + +
Fields inherited from class com.google.bitcoin.core.Message
bytes, cursor, MAX_SIZE, offset, params
+  + + + + + + + + + + +
+Constructor Summary
GetDataMessage(NetworkParameters params, + byte[] payloadBytes) + +
+           
+  + + + + + + + + + + + + + + + +
+Method Summary
+ byte[]bitcoinSerialize() + +
+           
+ voidparse() + +
+           
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+GetDataMessage

+
+public GetDataMessage(NetworkParameters params,
+                      byte[] payloadBytes)
+               throws ProtocolException
+
+
+ +
Throws: +
ProtocolException
+
+ + + + + + + + +
+Method Detail
+ +

+bitcoinSerialize

+
+public byte[] bitcoinSerialize()
+
+
+
Overrides:
bitcoinSerialize in class Message
+
+
+
+
+
+
+ +

+parse

+
+public void parse()
+           throws ProtocolException
+
+
+
+
+
+ +
Throws: +
ProtocolException
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/InventoryItem.Type.html b/docs/com/google/bitcoin/core/InventoryItem.Type.html new file mode 100644 index 000000000..2fa9cbc0d --- /dev/null +++ b/docs/com/google/bitcoin/core/InventoryItem.Type.html @@ -0,0 +1,339 @@ + + + + + + +InventoryItem.Type + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Enum InventoryItem.Type

+
+java.lang.Object
+  extended by java.lang.Enum<InventoryItem.Type>
+      extended by com.google.bitcoin.core.InventoryItem.Type
+
+
+
All Implemented Interfaces:
java.io.Serializable, java.lang.Comparable<InventoryItem.Type>
+
+
+
Enclosing class:
InventoryItem
+
+
+
+
public static enum InventoryItem.Type
extends java.lang.Enum<InventoryItem.Type>
+ + +

+


+ +

+ + + + + + + + + + + + + + + + +
+Enum Constant Summary
Block + +
+           
Error + +
+           
Transaction + +
+           
+  + + + + + + + + + + + + + + + +
+Method Summary
+static InventoryItem.TypevalueOf(java.lang.String name) + +
+          Returns the enum constant of this type with the specified name.
+static InventoryItem.Type[]values() + +
+          Returns an array containing the constants of this enum type, in +the order they are declared.
+ + + + + + + +
Methods inherited from class java.lang.Enum
clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
+ + + + + + + +
Methods inherited from class java.lang.Object
getClass, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Enum Constant Detail
+ +

+Error

+
+public static final InventoryItem.Type Error
+
+
+
+
+
+ +

+Transaction

+
+public static final InventoryItem.Type Transaction
+
+
+
+
+
+ +

+Block

+
+public static final InventoryItem.Type Block
+
+
+
+
+ + + + + + + + +
+Method Detail
+ +

+values

+
+public static InventoryItem.Type[] values()
+
+
Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
+for (InventoryItem.Type c : InventoryItem.Type.values())
+    System.out.println(c);
+
+

+

+ +
Returns:
an array containing the constants of this enum type, in +the order they are declared
+
+
+
+ +

+valueOf

+
+public static InventoryItem.Type valueOf(java.lang.String name)
+
+
Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.) +

+

+
Parameters:
name - the name of the enum constant to be returned. +
Returns:
the enum constant with the specified name +
Throws: +
java.lang.IllegalArgumentException - if this enum type has no constant +with the specified name +
java.lang.NullPointerException - if the argument is null
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/InventoryItem.html b/docs/com/google/bitcoin/core/InventoryItem.html new file mode 100644 index 000000000..b4264f823 --- /dev/null +++ b/docs/com/google/bitcoin/core/InventoryItem.html @@ -0,0 +1,326 @@ + + + + + + +InventoryItem + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class InventoryItem

+
+java.lang.Object
+  extended by com.google.bitcoin.core.InventoryItem
+
+
+
+
public class InventoryItem
extends java.lang.Object
+ + +

+


+ +

+ + + + + + + + + + + +
+Nested Class Summary
+static classInventoryItem.Type + +
+           
+ + + + + + + + + + + + + + +
+Field Summary
+ byte[]hash + +
+           
+ InventoryItem.Typetype + +
+           
+  + + + + + + + + + + +
+Constructor Summary
InventoryItem(InventoryItem.Type type, + byte[] hash) + +
+           
+  + + + + + + + + + + + +
+Method Summary
+ java.lang.StringtoString() + +
+           
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Field Detail
+ +

+type

+
+public final InventoryItem.Type type
+
+
+
+
+
+ +

+hash

+
+public final byte[] hash
+
+
+
+
+ + + + + + + + +
+Constructor Detail
+ +

+InventoryItem

+
+public InventoryItem(InventoryItem.Type type,
+                     byte[] hash)
+
+
+ + + + + + + + +
+Method Detail
+ +

+toString

+
+public java.lang.String toString()
+
+
+
Overrides:
toString in class java.lang.Object
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/InventoryMessage.html b/docs/com/google/bitcoin/core/InventoryMessage.html new file mode 100644 index 000000000..daa1703a0 --- /dev/null +++ b/docs/com/google/bitcoin/core/InventoryMessage.html @@ -0,0 +1,365 @@ + + + + + + +InventoryMessage + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class InventoryMessage

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Message
+      extended by com.google.bitcoin.core.InventoryMessage
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class InventoryMessage
extends Message
+ + +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + + + + + +
+Field Summary
+ java.util.List<InventoryItem>items + +
+           
+ + + + + + + +
Fields inherited from class com.google.bitcoin.core.Message
bytes, cursor, MAX_SIZE, offset, params
+  + + + + + + + + + + + + + +
+Constructor Summary
InventoryMessage(NetworkParameters params) + +
+           
InventoryMessage(NetworkParameters params, + byte[] bytes) + +
+           
+  + + + + + + + + + + + + + + + +
+Method Summary
+ voidbitcoinSerializeToStream(java.io.OutputStream stream) + +
+          Serializes this message to the provided stream.
+ voidparse() + +
+           
+ + + + + + + +
Methods inherited from class com.google.bitcoin.core.Message
bitcoinSerialize
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Field Detail
+ +

+items

+
+public java.util.List<InventoryItem> items
+
+
+
+
+ + + + + + + + +
+Constructor Detail
+ +

+InventoryMessage

+
+public InventoryMessage(NetworkParameters params,
+                        byte[] bytes)
+                 throws ProtocolException
+
+
+ +
Throws: +
ProtocolException
+
+
+ +

+InventoryMessage

+
+public InventoryMessage(NetworkParameters params)
+
+
+ + + + + + + + +
+Method Detail
+ +

+parse

+
+public void parse()
+           throws ProtocolException
+
+
+
+
+
+ +
Throws: +
ProtocolException
+
+
+
+ +

+bitcoinSerializeToStream

+
+public void bitcoinSerializeToStream(java.io.OutputStream stream)
+                              throws java.io.IOException
+
+
Description copied from class: Message
+
Serializes this message to the provided stream. If you just want the raw bytes use bitcoinSerialize(). +

+

+
+
+
+ +
Throws: +
java.io.IOException
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/Message.html b/docs/com/google/bitcoin/core/Message.html new file mode 100644 index 000000000..cca40d8f5 --- /dev/null +++ b/docs/com/google/bitcoin/core/Message.html @@ -0,0 +1,381 @@ + + + + + + +Message + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class Message

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Message
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
Direct Known Subclasses:
AddressMessage, Block, GetBlocksMessage, GetDataMessage, InventoryMessage, PeerAddress, Transaction, TransactionInput, TransactionOutPoint, TransactionOutput, UnknownMessage, VersionMessage
+
+
+
+
public abstract class Message
extends java.lang.Object
implements java.io.Serializable
+ + +

+A Message is a data structure that can be serialized/deserialized using both the BitCoin proprietary serialization + format and built-in Java object serialization. Specific types of messages that are used both in the block chain, + and on the wire, are derived from this class. + + This class is not useful for library users. If you want to talk to the network see the Peer class. +

+ +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Field Summary
+protected  byte[]bytes + +
+           
+protected  intcursor + +
+           
+static intMAX_SIZE + +
+           
+protected  intoffset + +
+           
+protected  NetworkParametersparams + +
+           
+  + + + + + + + + + + + +
+Constructor Summary
+protected Message() + +
+          This exists for the Java serialization framework to use only.
+  + + + + + + + + + + + +
+Method Summary
+ byte[]bitcoinSerialize() + +
+           
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Field Detail
+ +

+MAX_SIZE

+
+public static final int MAX_SIZE
+
+
+
See Also:
Constant Field Values
+
+
+ +

+offset

+
+protected transient int offset
+
+
+
+
+
+ +

+cursor

+
+protected transient int cursor
+
+
+
+
+
+ +

+bytes

+
+protected transient byte[] bytes
+
+
+
+
+
+ +

+params

+
+protected NetworkParameters params
+
+
+
+
+ + + + + + + + +
+Constructor Detail
+ +

+Message

+
+protected Message()
+
+
This exists for the Java serialization framework to use only. +

+

+ + + + + + + + +
+Method Detail
+ +

+bitcoinSerialize

+
+public byte[] bitcoinSerialize()
+
+
+
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/NetworkConnection.html b/docs/com/google/bitcoin/core/NetworkConnection.html new file mode 100644 index 000000000..aa48d4c22 --- /dev/null +++ b/docs/com/google/bitcoin/core/NetworkConnection.html @@ -0,0 +1,354 @@ + + + + + + +NetworkConnection + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class NetworkConnection

+
+java.lang.Object
+  extended by com.google.bitcoin.core.NetworkConnection
+
+
+
+
public class NetworkConnection
extends java.lang.Object
+ + +

+A NetworkConnection handles talking to a remote BitCoin peer at a low level. It understands how to read and write + messages off the network, but doesn't asynchronously communicate with the peer or handle the higher level details + of the protocol. After constructing a NetworkConnection, use a Peer to hand off communication to a + background thread. + + Construction is blocking whilst the protocol version is negotiated. +

+ +

+


+ +

+ + + + + + + + + + + +
+Constructor Summary
NetworkConnection(java.net.InetAddress remoteIp, + NetworkParameters params) + +
+          Connect to the given IP address using the port specified as part of the network parameters.
+  + + + + + + + + + + + + + + + + + + + + + + + +
+Method Summary
+ voidping() + +
+          Sends a "ping" message to the remote node.
+ MessagereadMessage() + +
+          Reads a network message from the wire, blocking until the message is fully received.
+ voidshutdown() + +
+          Shuts down the network socket.
+ voidwriteMessage(java.lang.String tag, + Message message) + +
+          Writes the given message out over the network using the protocol tag.
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+NetworkConnection

+
+public NetworkConnection(java.net.InetAddress remoteIp,
+                         NetworkParameters params)
+                  throws java.io.IOException,
+                         ProtocolException
+
+
Connect to the given IP address using the port specified as part of the network parameters. Once construction + is complete a functioning network channel is set up and running. +

+

+
Parameters:
remoteIp - IP address to connect to. IPv6 is not currently supported by BitCoin.
params - Defines which network to connect to and details of the protocol. +
Throws: +
java.io.IOException - if there is a network related failure. +
ProtocolException - if the version negotiation failed.
+
+ + + + + + + + +
+Method Detail
+ +

+ping

+
+public void ping()
+          throws java.io.IOException
+
+
Sends a "ping" message to the remote node. The protocol doesn't presently use this feature much. +

+

+ +
Throws: +
java.io.IOException
+
+
+
+ +

+shutdown

+
+public void shutdown()
+              throws java.io.IOException
+
+
Shuts down the network socket. Note that there's no way to wait for a socket to be fully flushed out to the + wire, so if you call this immediately after sending a message it might not get sent. +

+

+ +
Throws: +
java.io.IOException
+
+
+
+ +

+readMessage

+
+public Message readMessage()
+                    throws ProtocolException
+
+
Reads a network message from the wire, blocking until the message is fully received. +

+

+ +
Returns:
An instance of a Message subclass. +
Throws: +
ProtocolException - if the message is badly formatted, failed checksum or there was a protocol failure.
+
+
+
+ +

+writeMessage

+
+public void writeMessage(java.lang.String tag,
+                         Message message)
+                  throws java.io.IOException
+
+
Writes the given message out over the network using the protocol tag. For a Transaction + this should be "tx" for example. It's safe to call this from multiple threads simultaneously, + the actual writing will be serialized. +

+

+ +
Throws: +
java.io.IOException
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/NetworkParameters.html b/docs/com/google/bitcoin/core/NetworkParameters.html new file mode 100644 index 000000000..c74c7c718 --- /dev/null +++ b/docs/com/google/bitcoin/core/NetworkParameters.html @@ -0,0 +1,409 @@ + + + + + + +NetworkParameters + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class NetworkParameters

+
+java.lang.Object
+  extended by com.google.bitcoin.core.NetworkParameters
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class NetworkParameters
extends java.lang.Object
implements java.io.Serializable
+ + +

+NetworkParameters contains the data needed for working with an instantiation of a BitCoin chain. + + Currently there are only two, the production chain and the test chain. But in future as BitCoin + evolves there may be more. You can create your own as long as they don't conflict. +

+ +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Field Summary
+ byteaddressHeader + +
+          First byte of a base58 encoded address.
+ BlockgenesisBlock + +
+          Genesis block for this chain
+ longpacketMagic + +
+          The header bytes that identify the start of a packet on this network.
+ intport + +
+          Default TCP port on which to connect to nodes.
+ java.math.BigIntegerproofOfWorkLimit + +
+          What the easiest allowable proof of work should be.
+  + + + + + + + + + + +
+Constructor Summary
NetworkParameters() + +
+           
+  + + + + + + + + + + + + + + + +
+Method Summary
+static NetworkParametersprodNet() + +
+          The primary BitCoin chain created by Satoshi.
+static NetworkParameterstestNet() + +
+          The test chain created by Gavin.
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Field Detail
+ +

+genesisBlock

+
+public Block genesisBlock
+
+
Genesis block for this chain +

+

+
+
+
+ +

+proofOfWorkLimit

+
+public java.math.BigInteger proofOfWorkLimit
+
+
What the easiest allowable proof of work should be. +

+

+
+
+
+ +

+port

+
+public int port
+
+
Default TCP port on which to connect to nodes. +

+

+
+
+
+ +

+packetMagic

+
+public long packetMagic
+
+
The header bytes that identify the start of a packet on this network. +

+

+
+
+
+ +

+addressHeader

+
+public byte addressHeader
+
+
First byte of a base58 encoded address. +

+

+
+
+ + + + + + + + +
+Constructor Detail
+ +

+NetworkParameters

+
+public NetworkParameters()
+
+
+ + + + + + + + +
+Method Detail
+ +

+testNet

+
+public static NetworkParameters testNet()
+
+
The test chain created by Gavin. +

+

+
+
+
+
+
+
+
+ +

+prodNet

+
+public static NetworkParameters prodNet()
+
+
The primary BitCoin chain created by Satoshi. +

+

+
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/Peer.html b/docs/com/google/bitcoin/core/Peer.html new file mode 100644 index 000000000..ce14edf3b --- /dev/null +++ b/docs/com/google/bitcoin/core/Peer.html @@ -0,0 +1,365 @@ + + + + + + +Peer + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class Peer

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Peer
+
+
+
+
public class Peer
extends java.lang.Object
+ + +

+A Peer handles the high level communication with a BitCoin node. It requires a NetworkConnection to be set up for + it. After that it takes ownership of the connection, creates and manages its own thread used for communication + with the network. All these threads synchronize on the block chain. +

+ +

+


+ +

+ + + + + + + + + + + +
+Constructor Summary
Peer(NetworkParameters params, + NetworkConnection conn, + BlockChain blockChain) + +
+          Construct a peer that handles the given network connection and reads/writes from the given block chain.
+  + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Method Summary
+ voidbroadcastTransaction(Transaction tx) + +
+          Send the given Transaction, ie, make a payment with BitCoins.
+ voiddisconnect() + +
+          Terminates the network connection and stops the background thread.
+ java.util.concurrent.Future<Block>getBlock(byte[] blockHash) + +
+          Asks the connected peer for the block of the given hash, and returns a Future representing the answer.
+ voidstart() + +
+          Starts the background thread that processes messages.
+ java.util.concurrent.CountDownLatchstartBlockChainDownload() + +
+          Starts an asynchronous download of the block chain.
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+Peer

+
+public Peer(NetworkParameters params,
+            NetworkConnection conn,
+            BlockChain blockChain)
+
+
Construct a peer that handles the given network connection and reads/writes from the given block chain. Note that + communication won't occur until you call start(). +

+

+ + + + + + + + +
+Method Detail
+ +

+start

+
+public void start()
+
+
Starts the background thread that processes messages. +

+

+
+
+
+
+ +

+getBlock

+
+public java.util.concurrent.Future<Block> getBlock(byte[] blockHash)
+                                            throws java.io.IOException
+
+
Asks the connected peer for the block of the given hash, and returns a Future representing the answer. + If you want the block right away and don't mind waiting for it, just call .get() on the result. Your thread + will block until the peer answers. You can also use the Future object to wait with a timeout, or just check + whether it's done later. +

+

+
Parameters:
blockHash - Hash of the block you wareare requesting. +
Throws: +
java.io.IOException
+
+
+
+ +

+broadcastTransaction

+
+public void broadcastTransaction(Transaction tx)
+                          throws java.io.IOException
+
+
Send the given Transaction, ie, make a payment with BitCoins. To create a transaction you can broadcast, use + a Wallet. After the broadcast completes, confirm the send using the wallet confirmSend() method. +

+

+ +
Throws: +
java.io.IOException
+
+
+
+ +

+startBlockChainDownload

+
+public java.util.concurrent.CountDownLatch startBlockChainDownload()
+                                                            throws java.io.IOException
+
+
Starts an asynchronous download of the block chain. Completion of the download is a somewhat vague concept in + BitCoin as the chain is constantly growing, but essentially we deem the download complete once we have + received the block that the peer told us was the head when we first started the download. +

+

+ +
Returns:
a CountDownLatch that can be used to wait until the chain download is "complete". +
Throws: +
java.io.IOException
+
+
+
+ +

+disconnect

+
+public void disconnect()
+
+
Terminates the network connection and stops the background thread. +

+

+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/PeerAddress.html b/docs/com/google/bitcoin/core/PeerAddress.html new file mode 100644 index 000000000..b4034fa21 --- /dev/null +++ b/docs/com/google/bitcoin/core/PeerAddress.html @@ -0,0 +1,369 @@ + + + + + + +PeerAddress + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class PeerAddress

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Message
+      extended by com.google.bitcoin.core.PeerAddress
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class PeerAddress
extends Message
+ + +

+A PeerAddress holds an IP address and port number representing the network location of + a peer in the BitCoin P2P network. It exists primarily for serialization purposes. +

+ +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + +
+Field Summary
+ + + + + + + +
Fields inherited from class com.google.bitcoin.core.Message
bytes, cursor, MAX_SIZE, offset, params
+  + + + + + + + + + + + + + +
+Constructor Summary
PeerAddress(java.net.InetAddress addr, + int port) + +
+           
PeerAddress(NetworkParameters params, + byte[] payload, + int offset) + +
+           
+  + + + + + + + + + + + + + + + + + + + +
+Method Summary
+ voidbitcoinSerializeToStream(java.io.OutputStream stream) + +
+          Serializes this message to the provided stream.
+protected  voidparse() + +
+           
+ java.lang.StringtoString() + +
+           
+ + + + + + + +
Methods inherited from class com.google.bitcoin.core.Message
bitcoinSerialize
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+PeerAddress

+
+public PeerAddress(NetworkParameters params,
+                   byte[] payload,
+                   int offset)
+            throws ProtocolException
+
+
+ +
Throws: +
ProtocolException
+
+
+ +

+PeerAddress

+
+public PeerAddress(java.net.InetAddress addr,
+                   int port)
+
+
+ + + + + + + + +
+Method Detail
+ +

+bitcoinSerializeToStream

+
+public void bitcoinSerializeToStream(java.io.OutputStream stream)
+                              throws java.io.IOException
+
+
Description copied from class: Message
+
Serializes this message to the provided stream. If you just want the raw bytes use bitcoinSerialize(). +

+

+
+
+
+ +
Throws: +
java.io.IOException
+
+
+
+ +

+parse

+
+protected void parse()
+              throws ProtocolException
+
+
+
+
+
+ +
Throws: +
ProtocolException
+
+
+
+ +

+toString

+
+public java.lang.String toString()
+
+
+
Overrides:
toString in class java.lang.Object
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/ProtocolException.html b/docs/com/google/bitcoin/core/ProtocolException.html new file mode 100644 index 000000000..82eb217cf --- /dev/null +++ b/docs/com/google/bitcoin/core/ProtocolException.html @@ -0,0 +1,268 @@ + + + + + + +ProtocolException + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class ProtocolException

+
+java.lang.Object
+  extended by java.lang.Throwable
+      extended by java.lang.Exception
+          extended by com.google.bitcoin.core.ProtocolException
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class ProtocolException
extends java.lang.Exception
+ + +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + + + + + + + + + + + +
+Constructor Summary
ProtocolException(java.lang.Exception e) + +
+           
ProtocolException(java.lang.String msg) + +
+           
ProtocolException(java.lang.String msg, + java.lang.Exception e) + +
+           
+  + + + + + + + +
+Method Summary
+ + + + + + + +
Methods inherited from class java.lang.Throwable
fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+ProtocolException

+
+public ProtocolException(java.lang.String msg)
+
+
+
+ +

+ProtocolException

+
+public ProtocolException(java.lang.Exception e)
+
+
+
+ +

+ProtocolException

+
+public ProtocolException(java.lang.String msg,
+                         java.lang.Exception e)
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/Script.html b/docs/com/google/bitcoin/core/Script.html new file mode 100644 index 000000000..d4521ea65 --- /dev/null +++ b/docs/com/google/bitcoin/core/Script.html @@ -0,0 +1,620 @@ + + + + + + +Script + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class Script

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Script
+
+
+
+
public class Script
extends java.lang.Object
+ + +

+BitCoin transactions don't specify what they do directly. Instead a + small binary stack language is used to define programs that when evaluated return whether the transaction + "accepts" or rejects the other transactions connected to it.

+ + This implementation of the scripting language is incomplete. It contains enough support to run standard + transactions generated by the official client, but non-standard transactions will fail. +

+ +

+


+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Field Summary
+static intOP_CHECKSIG + +
+           
+static intOP_DUP + +
+           
+static intOP_EQUALVERIFY + +
+           
+static intOP_HASH160 + +
+           
+static intOP_PUSHDATA1 + +
+           
+static intOP_PUSHDATA2 + +
+           
+static intOP_PUSHDATA4 + +
+           
+  + + + + + + + + + + +
+Constructor Summary
Script(NetworkParameters params, + byte[] programBytes, + int offset, + int length) + +
+          Construct a Script using the given network parameters and a range of the programBytes array.
+  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Method Summary
+ AddressgetFromAddress() + +
+          Convenience wrapper around getPubKey.
+ byte[]getPubKey() + +
+          If a program has two data buffers (constants) and nothing else, the second one is returned.
+ byte[]getPubKeyHash() + +
+          If a program matches the standard template DUP HASH160 EQUALVERIFY CHECKSIG + then this function retrieves the third element, otherwise it throws a ScriptException.
+ AddressgetToAddress() + +
+          Gets the destination address from this script, if it's in the required form (see getPubKey).
+ booleanisSentToIP() + +
+          Returns true if this transaction is of a format that means it was a direct IP to IP transaction.
+static Scriptjoin(Script a, + Script b) + +
+          Concatenates two scripts to form a new one.
+ booleanrun(Transaction context) + +
+          Runs the script with the given Transaction as the "context".
+ voidsetTracing(boolean value) + +
+          If true, running a program will log its instructions.
+ java.lang.StringtoString() + +
+          Returns the program opcodes as a string, for example "[1234] DUP HAHS160"
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Field Detail
+ +

+OP_PUSHDATA1

+
+public static final int OP_PUSHDATA1
+
+
+
See Also:
Constant Field Values
+
+
+ +

+OP_PUSHDATA2

+
+public static final int OP_PUSHDATA2
+
+
+
See Also:
Constant Field Values
+
+
+ +

+OP_PUSHDATA4

+
+public static final int OP_PUSHDATA4
+
+
+
See Also:
Constant Field Values
+
+
+ +

+OP_DUP

+
+public static final int OP_DUP
+
+
+
See Also:
Constant Field Values
+
+
+ +

+OP_HASH160

+
+public static final int OP_HASH160
+
+
+
See Also:
Constant Field Values
+
+
+ +

+OP_EQUALVERIFY

+
+public static final int OP_EQUALVERIFY
+
+
+
See Also:
Constant Field Values
+
+
+ +

+OP_CHECKSIG

+
+public static final int OP_CHECKSIG
+
+
+
See Also:
Constant Field Values
+
+ + + + + + + + +
+Constructor Detail
+ +

+Script

+
+public Script(NetworkParameters params,
+              byte[] programBytes,
+              int offset,
+              int length)
+       throws ScriptException
+
+
Construct a Script using the given network parameters and a range of the programBytes array. +

+

+
Parameters:
params - Network parameters.
programBytes - Array of program bytes from a transaction.
offset - How many bytes into programBytes to start reading from.
length - How many bytes to read. +
Throws: +
ScriptException
+
+ + + + + + + + +
+Method Detail
+ +

+join

+
+public static Script join(Script a,
+                          Script b)
+                   throws ScriptException
+
+
Concatenates two scripts to form a new one. This is used when verifying transactions. +

+

+ +
Throws: +
ScriptException
+
+
+
+ +

+setTracing

+
+public void setTracing(boolean value)
+
+
If true, running a program will log its instructions. +

+

+
+
+
+
+ +

+toString

+
+public java.lang.String toString()
+
+
Returns the program opcodes as a string, for example "[1234] DUP HAHS160" +

+

+
Overrides:
toString in class java.lang.Object
+
+
+
+
+
+
+ +

+isSentToIP

+
+public boolean isSentToIP()
+
+
Returns true if this transaction is of a format that means it was a direct IP to IP transaction. These + transactions are deprecated and no longer used, support for creating them has been removed from the official + client. +

+

+
+
+
+
+ +

+getPubKeyHash

+
+public byte[] getPubKeyHash()
+                     throws ScriptException
+
+
If a program matches the standard template DUP HASH160 EQUALVERIFY CHECKSIG + then this function retrieves the third element, otherwise it throws a ScriptException. + + This is useful for fetching the destination address of a transaction. +

+

+ +
Throws: +
ScriptException
+
+
+
+ +

+getPubKey

+
+public byte[] getPubKey()
+                 throws ScriptException
+
+
If a program has two data buffers (constants) and nothing else, the second one is returned. + For a scriptSig this should be the public key of the sender. + + This is useful for fetching the source address of a transaction. +

+

+ +
Throws: +
ScriptException
+
+
+
+ +

+getFromAddress

+
+public Address getFromAddress()
+                       throws ScriptException
+
+
Convenience wrapper around getPubKey. Only works for scriptSigs. +

+

+ +
Throws: +
ScriptException
+
+
+
+ +

+getToAddress

+
+public Address getToAddress()
+                     throws ScriptException
+
+
Gets the destination address from this script, if it's in the required form (see getPubKey). +

+

+ +
Throws: +
ScriptException
+
+
+
+ +

+run

+
+public boolean run(Transaction context)
+            throws ScriptException
+
+
Runs the script with the given Transaction as the "context". Some operations like CHECKSIG + require a transaction to operate on (eg to hash). The context transaction is typically + the transaction having its inputs verified, ie the one where the scriptSig comes from. +

+

+ +
Throws: +
ScriptException
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/ScriptException.html b/docs/com/google/bitcoin/core/ScriptException.html new file mode 100644 index 000000000..ce6919914 --- /dev/null +++ b/docs/com/google/bitcoin/core/ScriptException.html @@ -0,0 +1,254 @@ + + + + + + +ScriptException + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class ScriptException

+
+java.lang.Object
+  extended by java.lang.Throwable
+      extended by java.lang.Exception
+          extended by com.google.bitcoin.core.ScriptException
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class ScriptException
extends java.lang.Exception
+ + +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + + + + + + + + +
+Constructor Summary
ScriptException(java.lang.String msg) + +
+           
ScriptException(java.lang.String msg, + java.lang.Exception e) + +
+           
+  + + + + + + + +
+Method Summary
+ + + + + + + +
Methods inherited from class java.lang.Throwable
fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+ScriptException

+
+public ScriptException(java.lang.String msg)
+
+
+
+ +

+ScriptException

+
+public ScriptException(java.lang.String msg,
+                       java.lang.Exception e)
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/Transaction.SigHash.html b/docs/com/google/bitcoin/core/Transaction.SigHash.html new file mode 100644 index 000000000..db18dbbd8 --- /dev/null +++ b/docs/com/google/bitcoin/core/Transaction.SigHash.html @@ -0,0 +1,349 @@ + + + + + + +Transaction.SigHash + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Enum Transaction.SigHash

+
+java.lang.Object
+  extended by java.lang.Enum<Transaction.SigHash>
+      extended by com.google.bitcoin.core.Transaction.SigHash
+
+
+
All Implemented Interfaces:
java.io.Serializable, java.lang.Comparable<Transaction.SigHash>
+
+
+
Enclosing class:
Transaction
+
+
+
+
public static enum Transaction.SigHash
extends java.lang.Enum<Transaction.SigHash>
+ + +

+These constants are a part of a scriptSig signature on the inputs. They define the details of how a + transaction can be redeemed, specifically, they control how the hash of the transaction is calculated. + + Note: in the official client, this enum also has another flag, SIGHASH_ANYONECANPAY. In this implementation, + that's kept separate. + + Also note: only SIGHASH_ALL is actually used in the official client today. +

+ +

+


+ +

+ + + + + + + + + + + + + + + + +
+Enum Constant Summary
ALL + +
+           
NONE + +
+           
SINGLE + +
+           
+  + + + + + + + + + + + + + + + +
+Method Summary
+static Transaction.SigHashvalueOf(java.lang.String name) + +
+          Returns the enum constant of this type with the specified name.
+static Transaction.SigHash[]values() + +
+          Returns an array containing the constants of this enum type, in +the order they are declared.
+ + + + + + + +
Methods inherited from class java.lang.Enum
clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
+ + + + + + + +
Methods inherited from class java.lang.Object
getClass, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Enum Constant Detail
+ +

+ALL

+
+public static final Transaction.SigHash ALL
+
+
+
+
+
+ +

+NONE

+
+public static final Transaction.SigHash NONE
+
+
+
+
+
+ +

+SINGLE

+
+public static final Transaction.SigHash SINGLE
+
+
+
+
+ + + + + + + + +
+Method Detail
+ +

+values

+
+public static Transaction.SigHash[] values()
+
+
Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
+for (Transaction.SigHash c : Transaction.SigHash.values())
+    System.out.println(c);
+
+

+

+ +
Returns:
an array containing the constants of this enum type, in +the order they are declared
+
+
+
+ +

+valueOf

+
+public static Transaction.SigHash valueOf(java.lang.String name)
+
+
Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.) +

+

+
Parameters:
name - the name of the enum constant to be returned. +
Returns:
the enum constant with the specified name +
Throws: +
java.lang.IllegalArgumentException - if this enum type has no constant +with the specified name +
java.lang.NullPointerException - if the argument is null
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/Transaction.html b/docs/com/google/bitcoin/core/Transaction.html new file mode 100644 index 000000000..7bf8eddb6 --- /dev/null +++ b/docs/com/google/bitcoin/core/Transaction.html @@ -0,0 +1,660 @@ + + + + + + +Transaction + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class Transaction

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Message
+      extended by com.google.bitcoin.core.Transaction
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class Transaction
extends Message
implements java.io.Serializable
+ + +

+A transaction represents the movement of coins from some addresses to some other addresses. It can also represent + the minting of new coins. A Transaction object corresponds to the equivalent in the BitCoin C++ implementation.

+ + It implements TWO serialization protocols - the BitCoin proprietary format which is identical to the C++ + implementation and is used for reading/writing transactions to the wire and for hashing. It also implements Java + serialization which is used for the wallet. This allows us to easily add extra fields used for our own accounting + or UI purposes. +

+ +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + + + + + +
+Nested Class Summary
+static classTransaction.SigHash + +
+          These constants are a part of a scriptSig signature on the inputs.
+ + + + + + +
+Field Summary
+ + + + + + + +
Fields inherited from class com.google.bitcoin.core.Message
bytes, cursor, MAX_SIZE, offset, params
+  + + + + + + + + + + + + + +
+Constructor Summary
Transaction(NetworkParameters params, + byte[] payloadBytes) + +
+          Creates a transaction from the given serialized bytes, eg, from a block or a tx network message.
Transaction(NetworkParameters params, + byte[] payload, + int offset) + +
+          Creates a transaction by reading payload starting from offset bytes in.
+  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Method Summary
+ voidaddInput(TransactionOutput from) + +
+          Adds an input to this transaction that imports value from the given output.
+ voidaddOutput(TransactionOutput to) + +
+          Adds the given output to this transaction.
+ voidbitcoinSerializeToStream(java.io.OutputStream stream) + +
+          Serializes this message to the provided stream.
+ booleanequals(java.lang.Object other) + +
+           
+ byte[]getHash() + +
+          Returns the transaction hash as you see them in the block explorer.
+ java.lang.StringgetHashAsString() + +
+           
+ java.util.List<TransactionInput>getInputs() + +
+          Returns a read-only list of the inputs of this transaction.
+ java.math.BigIntegergetValueSentToMe(Wallet wallet) + +
+          Returns the sum of the outputs that are sending coins to a key in our wallet.
+ inthashCode() + +
+           
+ booleanisCoinBase() + +
+          A coinbase transaction is one that creates a new coin.
+ voidsignInputs(Transaction.SigHash hashType, + Wallet wallet) + +
+          Once a transaction has some inputs and outputs added, the signatures in the inputs can be calculated.
+ java.lang.StringtoString() + +
+           
+ booleanverifyInput(int inputIndex, + Transaction connectedTx) + +
+          Given a named input and the transaction output it connects to, runs the script formed from the + concatenation of the input and output scripts, returning true if the link is valid.
+ + + + + + + +
Methods inherited from class com.google.bitcoin.core.Message
bitcoinSerialize
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, finalize, getClass, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+Transaction

+
+public Transaction(NetworkParameters params,
+                   byte[] payloadBytes)
+            throws ProtocolException
+
+
Creates a transaction from the given serialized bytes, eg, from a block or a tx network message. +

+

+ +
Throws: +
ProtocolException
+
+
+ +

+Transaction

+
+public Transaction(NetworkParameters params,
+                   byte[] payload,
+                   int offset)
+            throws ProtocolException
+
+
Creates a transaction by reading payload starting from offset bytes in. Length of a transaction is fixed. +

+

+ +
Throws: +
ProtocolException
+
+ + + + + + + + +
+Method Detail
+ +

+getInputs

+
+public java.util.List<TransactionInput> getInputs()
+
+
Returns a read-only list of the inputs of this transaction. +

+

+
+
+
+
+
+
+
+ +

+getHash

+
+public byte[] getHash()
+
+
Returns the transaction hash as you see them in the block explorer. +

+

+
+
+
+
+
+
+
+ +

+getHashAsString

+
+public java.lang.String getHashAsString()
+
+
+
+
+
+
+
+
+
+ +

+getValueSentToMe

+
+public java.math.BigInteger getValueSentToMe(Wallet wallet)
+
+
Returns the sum of the outputs that are sending coins to a key in our wallet. +

+

+
+
+
+
+
+
+
+ +

+isCoinBase

+
+public boolean isCoinBase()
+
+
A coinbase transaction is one that creates a new coin. They are the first transaction in each block and their + value is determined by a formula that all implementations of BitCoin share. In 2011 the value of a coinbase + transaction is 50 coins, but in future it will be less. A coinbase transaction is defined not only by its + position in a block but by the data in the inputs. +

+

+
+
+
+
+
+
+
+ +

+toString

+
+public java.lang.String toString()
+
+
+
Overrides:
toString in class java.lang.Object
+
+
+ +
Returns:
A human readable version of the transaction useful for debugging.
+
+
+
+ +

+addInput

+
+public void addInput(TransactionOutput from)
+
+
Adds an input to this transaction that imports value from the given output. Note that this input is NOT + complete and after every input is added with addInput() and every output is added with addOutput(), + signInputs() must be called to finalize the transaction and finish the inputs off. Otherwise it won't be + accepted by the network. +

+

+
+
+
+
+
+
+
+ +

+addOutput

+
+public void addOutput(TransactionOutput to)
+
+
Adds the given output to this transaction. The output must be completely initialized. +

+

+
+
+
+
+
+
+
+ +

+signInputs

+
+public void signInputs(Transaction.SigHash hashType,
+                       Wallet wallet)
+                throws ScriptException
+
+
Once a transaction has some inputs and outputs added, the signatures in the inputs can be calculated. The + signature is over the transaction itself, to prove the redeemer actually created that transaction, + so we have to do this step last. + + This method is similar to SignatureHash in script.cpp +

+

+
+
+
+
Parameters:
hashType - This should always be set to SigHash.ALL currently. Other types are unused.
wallet - A wallet is required to fetch the keys needed for signing. +
Throws: +
ScriptException
+
+
+
+ +

+verifyInput

+
+public boolean verifyInput(int inputIndex,
+                           Transaction connectedTx)
+                    throws ScriptException
+
+
Given a named input and the transaction output it connects to, runs the script formed from the + concatenation of the input and output scripts, returning true if the link is valid. In + this way, we prove that the creator of this transaction is allowed to redeem the output + of the connectedTx and thus spend the money.

+ + WARNING: NOT FINISHED

+

+

+
+
+
+
Parameters:
inputIndex - Which input to verify.
connectedTx - The Transaction that the input is connected to. +
Throws: +
ScriptException
+
+
+
+ +

+bitcoinSerializeToStream

+
+public void bitcoinSerializeToStream(java.io.OutputStream stream)
+                              throws java.io.IOException
+
+
Description copied from class: Message
+
Serializes this message to the provided stream. If you just want the raw bytes use bitcoinSerialize(). +

+

+
+
+
+ +
Throws: +
java.io.IOException
+
+
+
+ +

+equals

+
+public boolean equals(java.lang.Object other)
+
+
+
Overrides:
equals in class java.lang.Object
+
+
+
+
+
+
+ +

+hashCode

+
+public int hashCode()
+
+
+
Overrides:
hashCode in class java.lang.Object
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/TransactionInput.html b/docs/com/google/bitcoin/core/TransactionInput.html new file mode 100644 index 000000000..53fc5e73e --- /dev/null +++ b/docs/com/google/bitcoin/core/TransactionInput.html @@ -0,0 +1,439 @@ + + + + + + +TransactionInput + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class TransactionInput

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Message
+      extended by com.google.bitcoin.core.TransactionInput
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class TransactionInput
extends Message
implements java.io.Serializable
+ + +

+A transfer of coins from one address to another creates a transaction in which the outputs + can be claimed by the recipient in the input of another transaction. You can imagine a + transaction as being a module which is wired up to others, the inputs of one have to be wired + to the outputs of another. The exceptions are coinbase transactions, which create new coins. +

+ +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + + + + + +
+Field Summary
+static byte[]EMPTY_ARRAY + +
+           
+ + + + + + + +
Fields inherited from class com.google.bitcoin.core.Message
bytes, cursor, MAX_SIZE, offset, params
+  + + + + + + + + + + +
+Constructor Summary
TransactionInput(NetworkParameters params, + byte[] payload, + int offset) + +
+          Deserializes an input message.
+  + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Method Summary
+ voidbitcoinSerializeToStream(java.io.OutputStream stream) + +
+          Serializes this message to the provided stream.
+ AddressgetFromAddress() + +
+          Convenience method that returns the from address of this input by parsing the scriptSig.
+ ScriptgetScriptSig() + +
+          Returns the input script.
+ booleanisCoinBase() + +
+          Coinbase transactions have special inputs with hashes of zero.
+ java.lang.StringtoString() + +
+          Returns a human readable debug string.
+ + + + + + + +
Methods inherited from class com.google.bitcoin.core.Message
bitcoinSerialize
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Field Detail
+ +

+EMPTY_ARRAY

+
+public static final byte[] EMPTY_ARRAY
+
+
+
+
+ + + + + + + + +
+Constructor Detail
+ +

+TransactionInput

+
+public TransactionInput(NetworkParameters params,
+                        byte[] payload,
+                        int offset)
+                 throws ProtocolException
+
+
Deserializes an input message. This is usually part of a transaction message. +

+

+ +
Throws: +
ProtocolException
+
+ + + + + + + + +
+Method Detail
+ +

+bitcoinSerializeToStream

+
+public void bitcoinSerializeToStream(java.io.OutputStream stream)
+                              throws java.io.IOException
+
+
Description copied from class: Message
+
Serializes this message to the provided stream. If you just want the raw bytes use bitcoinSerialize(). +

+

+
+
+
+ +
Throws: +
java.io.IOException
+
+
+
+ +

+isCoinBase

+
+public boolean isCoinBase()
+
+
Coinbase transactions have special inputs with hashes of zero. If this is such an input, returns true. +

+

+
+
+
+
+
+
+
+ +

+getScriptSig

+
+public Script getScriptSig()
+                    throws ScriptException
+
+
Returns the input script. +

+

+
+
+
+ +
Throws: +
ScriptException
+
+
+
+ +

+getFromAddress

+
+public Address getFromAddress()
+                       throws ScriptException
+
+
Convenience method that returns the from address of this input by parsing the scriptSig. +

+

+
+
+
+ +
Throws: +
ScriptException - if the scriptSig could not be understood (eg, if this is a coinbase transaction).
+
+
+
+ +

+toString

+
+public java.lang.String toString()
+
+
Returns a human readable debug string. +

+

+
Overrides:
toString in class java.lang.Object
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/TransactionOutPoint.html b/docs/com/google/bitcoin/core/TransactionOutPoint.html new file mode 100644 index 000000000..0003cabb0 --- /dev/null +++ b/docs/com/google/bitcoin/core/TransactionOutPoint.html @@ -0,0 +1,307 @@ + + + + + + +TransactionOutPoint + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class TransactionOutPoint

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Message
+      extended by com.google.bitcoin.core.TransactionOutPoint
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class TransactionOutPoint
extends Message
implements java.io.Serializable
+ + +

+This message is effectively a reference or pointer to a transaction output. +

+ +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + +
+Field Summary
+ + + + + + + +
Fields inherited from class com.google.bitcoin.core.Message
bytes, cursor, MAX_SIZE, offset, params
+  + + + + + + + + + + +
+Constructor Summary
TransactionOutPoint(NetworkParameters params, + byte[] payload, + int offset) + +
+          Deserializes the message.
+  + + + + + + + + + + + +
+Method Summary
+ voidbitcoinSerializeToStream(java.io.OutputStream stream) + +
+          Serializes this message to the provided stream.
+ + + + + + + +
Methods inherited from class com.google.bitcoin.core.Message
bitcoinSerialize
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+TransactionOutPoint

+
+public TransactionOutPoint(NetworkParameters params,
+                           byte[] payload,
+                           int offset)
+                    throws ProtocolException
+
+
Deserializes the message. This is usually part of a transaction message. +

+

+ +
Throws: +
ProtocolException
+
+ + + + + + + + +
+Method Detail
+ +

+bitcoinSerializeToStream

+
+public void bitcoinSerializeToStream(java.io.OutputStream stream)
+                              throws java.io.IOException
+
+
Description copied from class: Message
+
Serializes this message to the provided stream. If you just want the raw bytes use bitcoinSerialize(). +

+

+
+
+
+ +
Throws: +
java.io.IOException
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/TransactionOutput.html b/docs/com/google/bitcoin/core/TransactionOutput.html new file mode 100644 index 000000000..f3b73bb2a --- /dev/null +++ b/docs/com/google/bitcoin/core/TransactionOutput.html @@ -0,0 +1,430 @@ + + + + + + +TransactionOutput + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class TransactionOutput

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Message
+      extended by com.google.bitcoin.core.TransactionOutput
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class TransactionOutput
extends Message
implements java.io.Serializable
+ + +

+A TransactionOutput message contains a scriptPubKey that controls who is able to spend its value. It is a sub-part + of the Transaction message. +

+ +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + +
+Field Summary
+ + + + + + + +
Fields inherited from class com.google.bitcoin.core.Message
bytes, cursor, MAX_SIZE, offset, params
+  + + + + + + + + + + +
+Constructor Summary
TransactionOutput(NetworkParameters params, + Transaction parent, + byte[] payload, + int offset) + +
+          Deserializes a transaction output message.
+  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Method Summary
+ voidbitcoinSerializeToStream(java.io.OutputStream stream) + +
+          Serializes this message to the provided stream.
+ byte[]getScriptBytes() + +
+           
+ ScriptgetScriptPubKey() + +
+           
+ java.math.BigIntegergetValue() + +
+          Returns the value of this output in nanocoins.
+ booleanisMine(Wallet wallet) + +
+          Returns true if this output is to an address we have the keys for in the wallet.
+ java.lang.StringtoString() + +
+          Returns a human readable debug string.
+ + + + + + + +
Methods inherited from class com.google.bitcoin.core.Message
bitcoinSerialize
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+TransactionOutput

+
+public TransactionOutput(NetworkParameters params,
+                         Transaction parent,
+                         byte[] payload,
+                         int offset)
+                  throws ProtocolException
+
+
Deserializes a transaction output message. This is usually part of a transaction message. +

+

+ +
Throws: +
ProtocolException
+
+ + + + + + + + +
+Method Detail
+ +

+getScriptPubKey

+
+public Script getScriptPubKey()
+                       throws ScriptException
+
+
+
+
+
+ +
Throws: +
ScriptException
+
+
+
+ +

+bitcoinSerializeToStream

+
+public void bitcoinSerializeToStream(java.io.OutputStream stream)
+                              throws java.io.IOException
+
+
Description copied from class: Message
+
Serializes this message to the provided stream. If you just want the raw bytes use bitcoinSerialize(). +

+

+
+
+
+ +
Throws: +
java.io.IOException
+
+
+
+ +

+getValue

+
+public java.math.BigInteger getValue()
+
+
Returns the value of this output in nanocoins. This is the amount of currency that the destination address + receives. +

+

+
+
+
+
+
+
+
+ +

+getScriptBytes

+
+public byte[] getScriptBytes()
+
+
+
+
+
+
+
+
+
+ +

+isMine

+
+public boolean isMine(Wallet wallet)
+
+
Returns true if this output is to an address we have the keys for in the wallet. +

+

+
+
+
+
+
+
+
+ +

+toString

+
+public java.lang.String toString()
+
+
Returns a human readable debug string. +

+

+
Overrides:
toString in class java.lang.Object
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/UnknownMessage.html b/docs/com/google/bitcoin/core/UnknownMessage.html new file mode 100644 index 000000000..38e7ebb77 --- /dev/null +++ b/docs/com/google/bitcoin/core/UnknownMessage.html @@ -0,0 +1,320 @@ + + + + + + +UnknownMessage + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class UnknownMessage

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Message
+      extended by com.google.bitcoin.core.UnknownMessage
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class UnknownMessage
extends Message
+ + +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + +
+Field Summary
+ + + + + + + +
Fields inherited from class com.google.bitcoin.core.Message
bytes, cursor, MAX_SIZE, offset, params
+  + + + + + + + + + + +
+Constructor Summary
UnknownMessage(NetworkParameters params, + java.lang.String name, + byte[] payloadBytes) + +
+           
+  + + + + + + + + + + + + + + + +
+Method Summary
+ voidparse() + +
+           
+ java.lang.StringtoString() + +
+           
+ + + + + + + +
Methods inherited from class com.google.bitcoin.core.Message
bitcoinSerialize
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+UnknownMessage

+
+public UnknownMessage(NetworkParameters params,
+                      java.lang.String name,
+                      byte[] payloadBytes)
+               throws ProtocolException
+
+
+ +
Throws: +
ProtocolException
+
+ + + + + + + + +
+Method Detail
+ +

+toString

+
+public java.lang.String toString()
+
+
+
Overrides:
toString in class java.lang.Object
+
+
+
+
+
+
+ +

+parse

+
+public void parse()
+           throws ProtocolException
+
+
+
+
+
+ +
Throws: +
ProtocolException
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/Utils.html b/docs/com/google/bitcoin/core/Utils.html new file mode 100644 index 000000000..8ce38d68c --- /dev/null +++ b/docs/com/google/bitcoin/core/Utils.html @@ -0,0 +1,637 @@ + + + + + + +Utils + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class Utils

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Utils
+
+
+
+
public class Utils
extends java.lang.Object
+ + +

+A collection of various utility methods that are helpful for working with the BitCoin protocol. +

+ +

+


+ +

+ + + + + + + + + + + + + + + +
+Field Summary
+static java.math.BigIntegerCENT + +
+          How many nanocoins there are in 0.01 BitCoins.
+static java.math.BigIntegerCOIN + +
+          How many nanocoins there are in a BitCoin.
+  + + + + + + + + + + +
+Constructor Summary
Utils() + +
+           
+  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Method Summary
+static java.lang.StringbitcoinValueToFriendlyString(java.math.BigInteger value) + +
+          Returns the given value in nanocoins as a 0.12 type string.
+static java.lang.StringbytesToHexString(byte[] bytes) + +
+          Returns the given byte array hex encoded.
+static byte[]doubleDigest(byte[] input) + +
+          See doubleDigest(byte[],int,int).
+static byte[]doubleDigest(byte[] input, + int offset, + int length) + +
+          Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again.
+static byte[]doubleDigestTwoBuffers(byte[] input1, + int offset1, + int length1, + byte[] input2, + int offset2, + int length2) + +
+          Calculates SHA256(SHA256(byte range 1 + byte range 2)).
+static booleanisLessThanUnsigned(long n1, + long n2) + +
+          Work around lack of unsigned types in Java.
+static longreadUint32(byte[] bytes, + int offset) + +
+           
+static longreadUint32BE(byte[] bytes, + int offset) + +
+           
+static byte[]reverseBytes(byte[] bytes) + +
+          Returns a copy of the given byte array in reverse order.
+static byte[]sha256hash160(byte[] input) + +
+          Calculates RIPEMD160(SHA256(input)).
+static java.math.BigIntegertoNanoCoins(int coins, + int cents) + +
+          Convert an amount expressed in the way humans are used to into nanocoins.
+static voiduint32ToByteArrayBE(long val, + byte[] out, + int offset) + +
+           
+static voiduint32ToByteArrayLE(long val, + byte[] out, + int offset) + +
+           
+static voiduint32ToByteStreamLE(long val, + java.io.OutputStream stream) + +
+           
+static voiduint64ToByteStreamLE(java.math.BigInteger val, + java.io.OutputStream stream) + +
+           
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Field Detail
+ +

+COIN

+
+public static final java.math.BigInteger COIN
+
+
How many nanocoins there are in a BitCoin. +

+

+
+
+
+ +

+CENT

+
+public static final java.math.BigInteger CENT
+
+
How many nanocoins there are in 0.01 BitCoins. +

+

+
+
+ + + + + + + + +
+Constructor Detail
+ +

+Utils

+
+public Utils()
+
+
+ + + + + + + + +
+Method Detail
+ +

+toNanoCoins

+
+public static java.math.BigInteger toNanoCoins(int coins,
+                                               int cents)
+
+
Convert an amount expressed in the way humans are used to into nanocoins. +

+

+
+
+
+
+ +

+uint32ToByteArrayBE

+
+public static void uint32ToByteArrayBE(long val,
+                                       byte[] out,
+                                       int offset)
+
+
+
+
+
+
+ +

+uint32ToByteArrayLE

+
+public static void uint32ToByteArrayLE(long val,
+                                       byte[] out,
+                                       int offset)
+
+
+
+
+
+
+ +

+uint32ToByteStreamLE

+
+public static void uint32ToByteStreamLE(long val,
+                                        java.io.OutputStream stream)
+                                 throws java.io.IOException
+
+
+ +
Throws: +
java.io.IOException
+
+
+
+ +

+uint64ToByteStreamLE

+
+public static void uint64ToByteStreamLE(java.math.BigInteger val,
+                                        java.io.OutputStream stream)
+                                 throws java.io.IOException
+
+
+ +
Throws: +
java.io.IOException
+
+
+
+ +

+doubleDigest

+
+public static byte[] doubleDigest(byte[] input)
+
+
See doubleDigest(byte[],int,int). +

+

+
+
+
+
+ +

+doubleDigest

+
+public static byte[] doubleDigest(byte[] input,
+                                  int offset,
+                                  int length)
+
+
Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again. This is + standard procedure in BitCoin. The resulting hash is in big endian form. +

+

+
+
+
+
+ +

+doubleDigestTwoBuffers

+
+public static byte[] doubleDigestTwoBuffers(byte[] input1,
+                                            int offset1,
+                                            int length1,
+                                            byte[] input2,
+                                            int offset2,
+                                            int length2)
+
+
Calculates SHA256(SHA256(byte range 1 + byte range 2)). +

+

+
+
+
+
+ +

+isLessThanUnsigned

+
+public static boolean isLessThanUnsigned(long n1,
+                                         long n2)
+
+
Work around lack of unsigned types in Java. +

+

+
+
+
+
+ +

+bytesToHexString

+
+public static java.lang.String bytesToHexString(byte[] bytes)
+
+
Returns the given byte array hex encoded. +

+

+
+
+
+
+ +

+reverseBytes

+
+public static byte[] reverseBytes(byte[] bytes)
+
+
Returns a copy of the given byte array in reverse order. +

+

+
+
+
+
+ +

+readUint32

+
+public static long readUint32(byte[] bytes,
+                              int offset)
+
+
+
+
+
+
+ +

+readUint32BE

+
+public static long readUint32BE(byte[] bytes,
+                                int offset)
+
+
+
+
+
+
+ +

+sha256hash160

+
+public static byte[] sha256hash160(byte[] input)
+
+
Calculates RIPEMD160(SHA256(input)). This is used in Address calculations. +

+

+
+
+
+
+ +

+bitcoinValueToFriendlyString

+
+public static java.lang.String bitcoinValueToFriendlyString(java.math.BigInteger value)
+
+
Returns the given value in nanocoins as a 0.12 type string. +

+

+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/VarInt.html b/docs/com/google/bitcoin/core/VarInt.html new file mode 100644 index 000000000..35cd03507 --- /dev/null +++ b/docs/com/google/bitcoin/core/VarInt.html @@ -0,0 +1,340 @@ + + + + + + +VarInt + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class VarInt

+
+java.lang.Object
+  extended by com.google.bitcoin.core.VarInt
+
+
+
+
public class VarInt
extends java.lang.Object
+ + +

+


+ +

+ + + + + + + + + + + +
+Field Summary
+ longvalue + +
+           
+  + + + + + + + + + + + + + +
+Constructor Summary
VarInt(byte[] buf, + int offset) + +
+           
VarInt(long value) + +
+           
+  + + + + + + + + + + + + + + + + + + + +
+Method Summary
+ byte[]encode() + +
+           
+ byte[]encodeBE() + +
+           
+ intgetSizeInBytes() + +
+           
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Field Detail
+ +

+value

+
+public final long value
+
+
+
+
+ + + + + + + + +
+Constructor Detail
+ +

+VarInt

+
+public VarInt(long value)
+
+
+
+ +

+VarInt

+
+public VarInt(byte[] buf,
+              int offset)
+
+
+ + + + + + + + +
+Method Detail
+ +

+getSizeInBytes

+
+public int getSizeInBytes()
+
+
+
+
+
+
+ +

+encode

+
+public byte[] encode()
+
+
+
+
+
+
+ +

+encodeBE

+
+public byte[] encodeBE()
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/VerificationException.html b/docs/com/google/bitcoin/core/VerificationException.html new file mode 100644 index 000000000..7e4dba3b4 --- /dev/null +++ b/docs/com/google/bitcoin/core/VerificationException.html @@ -0,0 +1,238 @@ + + + + + + +VerificationException + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class VerificationException

+
+java.lang.Object
+  extended by java.lang.Throwable
+      extended by java.lang.Exception
+          extended by com.google.bitcoin.core.VerificationException
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class VerificationException
extends java.lang.Exception
+ + +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + + + + + +
+Constructor Summary
VerificationException(java.lang.String msg) + +
+           
+  + + + + + + + +
+Method Summary
+ + + + + + + +
Methods inherited from class java.lang.Throwable
fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+VerificationException

+
+public VerificationException(java.lang.String msg)
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/VersionMessage.html b/docs/com/google/bitcoin/core/VersionMessage.html new file mode 100644 index 000000000..34036029e --- /dev/null +++ b/docs/com/google/bitcoin/core/VersionMessage.html @@ -0,0 +1,419 @@ + + + + + + +VersionMessage + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class VersionMessage

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Message
+      extended by com.google.bitcoin.core.VersionMessage
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class VersionMessage
extends Message
+ + +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + + + + + + + + + + + + + + + + + +
+Field Summary
+ intclientVersion + +
+           
+ intlocalServices + +
+           
+static intPROTOCOL_VERSION + +
+           
+ java.math.BigIntegertime + +
+           
+ + + + + + + +
Fields inherited from class com.google.bitcoin.core.Message
bytes, cursor, MAX_SIZE, offset, params
+  + + + + + + + + + + + + + +
+Constructor Summary
VersionMessage(NetworkParameters params) + +
+           
VersionMessage(NetworkParameters params, + byte[] msg) + +
+           
+  + + + + + + + + + + + + + + + +
+Method Summary
+ voidbitcoinSerializeToStream(java.io.OutputStream buf) + +
+          Serializes this message to the provided stream.
+ voidparse() + +
+           
+ + + + + + + +
Methods inherited from class com.google.bitcoin.core.Message
bitcoinSerialize
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Field Detail
+ +

+PROTOCOL_VERSION

+
+public static final int PROTOCOL_VERSION
+
+
+
See Also:
Constant Field Values
+
+
+ +

+clientVersion

+
+public int clientVersion
+
+
+
+
+
+ +

+localServices

+
+public int localServices
+
+
+
+
+
+ +

+time

+
+public java.math.BigInteger time
+
+
+
+
+ + + + + + + + +
+Constructor Detail
+ +

+VersionMessage

+
+public VersionMessage(NetworkParameters params,
+                      byte[] msg)
+               throws ProtocolException
+
+
+ +
Throws: +
ProtocolException
+
+
+ +

+VersionMessage

+
+public VersionMessage(NetworkParameters params)
+
+
+ + + + + + + + +
+Method Detail
+ +

+parse

+
+public void parse()
+           throws ProtocolException
+
+
+
+
+
+ +
Throws: +
ProtocolException
+
+
+
+ +

+bitcoinSerializeToStream

+
+public void bitcoinSerializeToStream(java.io.OutputStream buf)
+                              throws java.io.IOException
+
+
Description copied from class: Message
+
Serializes this message to the provided stream. If you just want the raw bytes use bitcoinSerialize(). +

+

+
+
+
+ +
Throws: +
java.io.IOException
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/Wallet.html b/docs/com/google/bitcoin/core/Wallet.html new file mode 100644 index 000000000..3d5b83963 --- /dev/null +++ b/docs/com/google/bitcoin/core/Wallet.html @@ -0,0 +1,518 @@ + + + + + + +Wallet + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Class Wallet

+
+java.lang.Object
+  extended by com.google.bitcoin.core.Wallet
+
+
+
All Implemented Interfaces:
java.io.Serializable
+
+
+
+
public class Wallet
extends java.lang.Object
implements java.io.Serializable
+ + +

+A Wallet stores keys and a record of transactions that have not yet been spent. Thus, it is capable of + providing transactions on demand that meet a given combined value. Once a transaction + output is used, it is removed from the wallet as it is no longer available for spending.

+ + The Wallet is read and written from disk, so be sure to follow the Java serialization + versioning rules here. We use the built in Java serialization to avoid the need to + pull in a potentially large (code-size) third party serialization library.

+

+ +

+

+
See Also:
Serialized Form
+
+ +

+ + + + + + + + + + + + + + + +
+Field Summary
+ java.util.ArrayList<ECKey>keychain + +
+           
+ java.util.ArrayList<Transaction>unspent + +
+           
+  + + + + + + + + + + +
+Constructor Summary
Wallet(NetworkParameters params) + +
+          Creates a new, empty wallet with no keys and no transactions.
+  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Method Summary
+ voidaddEventListener(WalletEventListener listener) + +
+          Adds an event listener object.
+ voidaddKey(ECKey key) + +
+          Adds the given ECKey to the wallet.
+ ECKeyfindKeyFromPubHash(byte[] pubkeyHash) + +
+          Locates a keypair from the keychain given the hash of the public key.
+ java.math.BigIntegergetBalance() + +
+          Returns the balance of this wallet in nanocoins by summing up all unspent outputs that were sent to us.
+ booleanisTransactionPresent(Transaction transaction) + +
+          Returns true if the given transaction is present in the wallet, comparing by hash value (not by object + reference).
+static WalletloadFromFile(java.io.File f) + +
+          Returns a wallet deserialized from the given file.
+ voidsaveToFile(java.io.File f) + +
+          Uses Java serialization to save the wallet to the given file.
+ TransactionsendCoins(Peer peer, + Address to, + java.math.BigInteger nanocoins) + +
+          Sends coins to the given address, via the given Peer.
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Field Detail
+ +

+unspent

+
+public final java.util.ArrayList<Transaction> unspent
+
+
+
+
+
+ +

+keychain

+
+public final java.util.ArrayList<ECKey> keychain
+
+
+
+
+ + + + + + + + +
+Constructor Detail
+ +

+Wallet

+
+public Wallet(NetworkParameters params)
+
+
Creates a new, empty wallet with no keys and no transactions. If you want to restore a wallet from disk instead, + see loadFromFile. +

+

+ + + + + + + + +
+Method Detail
+ +

+saveToFile

+
+public void saveToFile(java.io.File f)
+                throws java.io.IOException
+
+
Uses Java serialization to save the wallet to the given file. +

+

+
+
+
+ +
Throws: +
java.io.IOException
+
+
+
+ +

+loadFromFile

+
+public static Wallet loadFromFile(java.io.File f)
+                           throws java.io.IOException
+
+
Returns a wallet deserialized from the given file. +

+

+
+
+
+ +
Throws: +
java.io.IOException
+
+
+
+ +

+isTransactionPresent

+
+public boolean isTransactionPresent(Transaction transaction)
+
+
Returns true if the given transaction is present in the wallet, comparing by hash value (not by object + reference). So you can create a transaction object from scratch and get true from this method if the + transaction is logically equal. +

+

+
+
+
+
+
+
+
+ +

+addEventListener

+
+public void addEventListener(WalletEventListener listener)
+
+
Adds an event listener object. Methods on this object are called when something interesting happens, + like receiving money.

+ + Threading: Event listener methods are dispatched on library provided threads and the both the wallet and the + listener objects are locked during dispatch, so your listeners do not have to be thread safe. However they + should not block as the Peer will be unresponsive to network traffic whilst your listener is running. +

+

+
+
+
+
+
+
+
+ +

+sendCoins

+
+public Transaction sendCoins(Peer peer,
+                             Address to,
+                             java.math.BigInteger nanocoins)
+                      throws java.io.IOException
+
+
Sends coins to the given address, via the given Peer. Change is returned to the first key in the wallet. +

+

+
+
+
+
Parameters:
to - Which address to send coins to.
nanocoins - How many nanocoins to send. You can use Utils.toNanoCoins() to calculate this. +
Returns:
The Transaction that was created or null if there was insufficient balance to send the coins. +
Throws: +
java.io.IOException - if there was a problem broadcasting the transaction
+
+
+
+ +

+addKey

+
+public void addKey(ECKey key)
+
+
Adds the given ECKey to the wallet. There is currently no way to delete keys (that would result in coin loss). +

+

+
+
+
+
+
+
+
+ +

+findKeyFromPubHash

+
+public ECKey findKeyFromPubHash(byte[] pubkeyHash)
+
+
Locates a keypair from the keychain given the hash of the public key. This is needed when finding out which + key we need to use to redeem a transaction output. Returns null if no key was found. +

+

+
+
+
+
+
+
+
+ +

+getBalance

+
+public java.math.BigInteger getBalance()
+
+
Returns the balance of this wallet in nanocoins by summing up all unspent outputs that were sent to us. +

+

+
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/WalletEventListener.html b/docs/com/google/bitcoin/core/WalletEventListener.html new file mode 100644 index 000000000..3d729a553 --- /dev/null +++ b/docs/com/google/bitcoin/core/WalletEventListener.html @@ -0,0 +1,219 @@ + + + + + + +WalletEventListener + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.core +
+Interface WalletEventListener

+
+
+
public interface WalletEventListener
+ + +

+Implementing WalletEventListener allows you to learn when a wallets balance has changed. +

+ +

+


+ +

+ + + + + + + + + + + + +
+Method Summary
+ voidonCoinsReceived(Wallet wallet, + Transaction tx, + java.math.BigInteger prevBalance, + java.math.BigInteger newBalance) + +
+          This is called on a Peer thread when a block is received that sends some coins to you.
+  +

+ + + + + + + + +
+Method Detail
+ +

+onCoinsReceived

+
+void onCoinsReceived(Wallet wallet,
+                     Transaction tx,
+                     java.math.BigInteger prevBalance,
+                     java.math.BigInteger newBalance)
+
+
This is called on a Peer thread when a block is received that sends some coins to you. Note that this will + also be called when downloading the block chain as the wallet balance catches up, + so if you don't want that register the event listener after the chain is downloaded. It's safe to use methods + of wallet during the execution of this callback. +

+

+
Parameters:
wallet - The wallet object that received the coins/
tx - The transaction which sent us the coins.
prevBalance - Balance before the coins were received.
newBalance - Current balance of the wallet.
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/package-frame.html b/docs/com/google/bitcoin/core/package-frame.html new file mode 100644 index 000000000..1ac15fe1c --- /dev/null +++ b/docs/com/google/bitcoin/core/package-frame.html @@ -0,0 +1,121 @@ + + + + + + +com.google.bitcoin.core + + + + + + + + + + + +com.google.bitcoin.core + + + + +
+Interfaces  + +
+WalletEventListener
+ + + + + + +
+Classes  + +
+Address +
+AddressMessage +
+Base58 +
+Block +
+BlockChain +
+ECKey +
+GetBlocksMessage +
+GetDataMessage +
+InventoryItem +
+InventoryMessage +
+Message +
+NetworkConnection +
+NetworkParameters +
+Peer +
+PeerAddress +
+Script +
+Transaction +
+TransactionInput +
+TransactionOutPoint +
+TransactionOutput +
+UnknownMessage +
+Utils +
+VarInt +
+VersionMessage +
+Wallet
+ + + + + + +
+Enums  + +
+InventoryItem.Type +
+Transaction.SigHash
+ + + + + + +
+Exceptions  + +
+AddressFormatException +
+ProtocolException +
+ScriptException +
+VerificationException
+ + + + diff --git a/docs/com/google/bitcoin/core/package-summary.html b/docs/com/google/bitcoin/core/package-summary.html new file mode 100644 index 000000000..b6c5a443f --- /dev/null +++ b/docs/com/google/bitcoin/core/package-summary.html @@ -0,0 +1,313 @@ + + + + + + +com.google.bitcoin.core + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+

+Package com.google.bitcoin.core +

+ + + + + + + + + +
+Interface Summary
WalletEventListenerImplementing WalletEventListener allows you to learn when a wallets balance has changed.
+  + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Class Summary
AddressA BitCoin address is fundamentally derived from an elliptic curve public key and a set of network parameters.
AddressMessage 
Base58A custom form of base58 is used to encode BitCoin addresses.
BlockA block is the foundation of the BitCoin system.
BlockChainA BlockChain holds a series of Block objects, links them together, and knows how to verify that the + chain follows the rules of the NetworkParameters for this chain.
ECKeyRepresents either an elliptic curve keypair that we own and can use for signing transactions.
GetBlocksMessage 
GetDataMessage 
InventoryItem 
InventoryMessage 
MessageA Message is a data structure that can be serialized/deserialized using both the BitCoin proprietary serialization + format and built-in Java object serialization.
NetworkConnectionA NetworkConnection handles talking to a remote BitCoin peer at a low level.
NetworkParametersNetworkParameters contains the data needed for working with an instantiation of a BitCoin chain.
PeerA Peer handles the high level communication with a BitCoin node.
PeerAddressA PeerAddress holds an IP address and port number representing the network location of + a peer in the BitCoin P2P network.
ScriptBitCoin transactions don't specify what they do directly.
TransactionA transaction represents the movement of coins from some addresses to some other addresses.
TransactionInputA transfer of coins from one address to another creates a transaction in which the outputs + can be claimed by the recipient in the input of another transaction.
TransactionOutPointThis message is effectively a reference or pointer to a transaction output.
TransactionOutputA TransactionOutput message contains a scriptPubKey that controls who is able to spend its value.
UnknownMessage 
UtilsA collection of various utility methods that are helpful for working with the BitCoin protocol.
VarInt 
VersionMessage 
WalletA Wallet stores keys and a record of transactions that have not yet been spent.
+  + +

+ + + + + + + + + + + + + +
+Enum Summary
InventoryItem.Type 
Transaction.SigHashThese constants are a part of a scriptSig signature on the inputs.
+  + +

+ + + + + + + + + + + + + + + + + + + + + +
+Exception Summary
AddressFormatException 
ProtocolException 
ScriptException 
VerificationException 
+  + +

+

+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/core/package-tree.html b/docs/com/google/bitcoin/core/package-tree.html new file mode 100644 index 000000000..e53e431b3 --- /dev/null +++ b/docs/com/google/bitcoin/core/package-tree.html @@ -0,0 +1,181 @@ + + + + + + +com.google.bitcoin.core Class Hierarchy + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+Hierarchy For Package com.google.bitcoin.core +

+
+
+
Package Hierarchies:
All Packages
+
+

+Class Hierarchy +

+ +

+Interface Hierarchy +

+ +

+Enum Hierarchy +

+ +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/examples/PingService.html b/docs/com/google/bitcoin/examples/PingService.html new file mode 100644 index 000000000..979d4593e --- /dev/null +++ b/docs/com/google/bitcoin/examples/PingService.html @@ -0,0 +1,258 @@ + + + + + + +PingService + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.examples +
+Class PingService

+
+java.lang.Object
+  extended by com.google.bitcoin.examples.PingService
+
+
+
+
public class PingService
extends java.lang.Object
+ + +

+PingService demonstrates basic usage of the library. It sits on the network and when it receives coins, simply + sends them right back to the previous owner, determined rather arbitrarily by the address of the first input. +

+ +

+


+ +

+ + + + + + + + + + + +
+Constructor Summary
PingService() + +
+           
+  + + + + + + + + + + + +
+Method Summary
+static voidmain(java.lang.String[] args) + +
+           
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+PingService

+
+public PingService()
+
+
+ + + + + + + + +
+Method Detail
+ +

+main

+
+public static void main(java.lang.String[] args)
+                 throws java.lang.Exception
+
+
+ +
Throws: +
java.lang.Exception
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/examples/PrivateKeys.html b/docs/com/google/bitcoin/examples/PrivateKeys.html new file mode 100644 index 000000000..1337aa1a4 --- /dev/null +++ b/docs/com/google/bitcoin/examples/PrivateKeys.html @@ -0,0 +1,262 @@ + + + + + + +PrivateKeys + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +com.google.bitcoin.examples +
+Class PrivateKeys

+
+java.lang.Object
+  extended by com.google.bitcoin.examples.PrivateKeys
+
+
+
+
public class PrivateKeys
extends java.lang.Object
+ + +

+This example shows how to solve the challenge Hal posted here: + + http://www.bitcoin.org/smf/index.php?topic=3638.0 + + in which a private key with some coins associated with it is published. The goal is to import the private key, + claim the coins and then send them to a different address. +

+ +

+


+ +

+ + + + + + + + + + + +
+Constructor Summary
PrivateKeys() + +
+           
+  + + + + + + + + + + + +
+Method Summary
+static voidmain(java.lang.String[] args) + +
+           
+ + + + + + + +
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
+  +

+ + + + + + + + +
+Constructor Detail
+ +

+PrivateKeys

+
+public PrivateKeys()
+
+
+ + + + + + + + +
+Method Detail
+ +

+main

+
+public static void main(java.lang.String[] args)
+                 throws java.lang.Exception
+
+
+ +
Throws: +
java.lang.Exception
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/examples/package-frame.html b/docs/com/google/bitcoin/examples/package-frame.html new file mode 100644 index 000000000..dedaea167 --- /dev/null +++ b/docs/com/google/bitcoin/examples/package-frame.html @@ -0,0 +1,34 @@ + + + + + + +com.google.bitcoin.examples + + + + + + + + + + + +com.google.bitcoin.examples + + + + +
+Classes  + +
+PingService +
+PrivateKeys
+ + + + diff --git a/docs/com/google/bitcoin/examples/package-summary.html b/docs/com/google/bitcoin/examples/package-summary.html new file mode 100644 index 000000000..5b957d4ec --- /dev/null +++ b/docs/com/google/bitcoin/examples/package-summary.html @@ -0,0 +1,163 @@ + + + + + + +com.google.bitcoin.examples + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+

+Package com.google.bitcoin.examples +

+ + + + + + + + + + + + + +
+Class Summary
PingServicePingService demonstrates basic usage of the library.
PrivateKeysThis example shows how to solve the challenge Hal posted here: + + http://www.bitcoin.org/smf/index.php?topic=3638.0 + + in which a private key with some coins associated with it is published.
+  + +

+

+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/com/google/bitcoin/examples/package-tree.html b/docs/com/google/bitcoin/examples/package-tree.html new file mode 100644 index 000000000..2cc30f88e --- /dev/null +++ b/docs/com/google/bitcoin/examples/package-tree.html @@ -0,0 +1,151 @@ + + + + + + +com.google.bitcoin.examples Class Hierarchy + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+Hierarchy For Package com.google.bitcoin.examples +

+
+
+
Package Hierarchies:
All Packages
+
+

+Class Hierarchy +

+ +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/constant-values.html b/docs/constant-values.html new file mode 100644 index 000000000..66fb0df48 --- /dev/null +++ b/docs/constant-values.html @@ -0,0 +1,262 @@ + + + + + + +Constant Field Values + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+Constant Field Values

+
+
+Contents + + + + + + +
+com.google.*
+ +

+ + + + + + + + + + + + +
com.google.bitcoin.core.Block
+public static final longALLOWED_TIME_DRIFT7200L
+ +

+ +

+ + + + + + + + + + + + +
com.google.bitcoin.core.Message
+public static final intMAX_SIZE33554432
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
com.google.bitcoin.core.Script
+public static final intOP_CHECKSIG172
+public static final intOP_DUP118
+public static final intOP_EQUALVERIFY136
+public static final intOP_HASH160169
+public static final intOP_PUSHDATA176
+public static final intOP_PUSHDATA277
+public static final intOP_PUSHDATA478
+ +

+ +

+ + + + + + + + + + + + +
com.google.bitcoin.core.VersionMessage
+public static final intPROTOCOL_VERSION31800
+ +

+ +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/deprecated-list.html b/docs/deprecated-list.html new file mode 100644 index 000000000..3f505dadf --- /dev/null +++ b/docs/deprecated-list.html @@ -0,0 +1,144 @@ + + + + + + +Deprecated List + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+Deprecated API

+
+
+Contents + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/help-doc.html b/docs/help-doc.html new file mode 100644 index 000000000..7327abf9f --- /dev/null +++ b/docs/help-doc.html @@ -0,0 +1,217 @@ + + + + + + +API Help + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+How This API Document Is Organized

+
+This API (Application Programming Interface) document has pages corresponding to the items in the navigation bar, described as follows.

+Overview

+
+ +

+The Overview page is the front page of this API document and provides a list of all packages with a summary for each. This page can also contain an overall description of the set of packages.

+

+Package

+
+ +

+Each package has a page that contains a list of its classes and interfaces, with a summary for each. This page can contain four categories:

+
+

+Class/Interface

+
+ +

+Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a class/interface description, summary tables, and detailed member descriptions:

+Each summary entry contains the first sentence from the detailed description for that item. The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.
+ +

+Annotation Type

+
+ +

+Each annotation type has its own separate page with the following sections:

+
+ +

+Enum

+
+ +

+Each enum has its own separate page with the following sections:

+
+

+Tree (Class Hierarchy)

+
+There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. The classes are organized by inheritance structure starting with java.lang.Object. The interfaces do not inherit from java.lang.Object. +
+

+Deprecated API

+
+The Deprecated API page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to improvements, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.
+

+Index

+
+The Index contains an alphabetic list of all classes, interfaces, constructors, methods, and fields.
+

+Prev/Next

+These links take you to the next or previous class, interface, package, or related page.

+Frames/No Frames

+These links show and hide the HTML frames. All pages are available with or without frames. +

+

+Serialized Form

+Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to re-implementors, not to developers using the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See also" section of the class description. +

+

+Constant Field Values

+The Constant Field Values page lists the static final fields and their values. +

+ + +This help file applies to API documentation generated using the standard doclet. + +
+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/index-all.html b/docs/index-all.html new file mode 100644 index 000000000..efe98c638 --- /dev/null +++ b/docs/index-all.html @@ -0,0 +1,838 @@ + + + + + + +Index + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +A B C D E F G H I J K L M N O P R S T U V W
+

+A

+
+
add(Block) - +Method in class com.google.bitcoin.core.BlockChain +
Processes a received block and tries to add it to the chain. +
addEventListener(WalletEventListener) - +Method in class com.google.bitcoin.core.Wallet +
Adds an event listener object. +
addInput(TransactionOutput) - +Method in class com.google.bitcoin.core.Transaction +
Adds an input to this transaction that imports value from the given output. +
addKey(ECKey) - +Method in class com.google.bitcoin.core.Wallet +
Adds the given ECKey to the wallet. +
addOutput(TransactionOutput) - +Method in class com.google.bitcoin.core.Transaction +
Adds the given output to this transaction. +
Address - Class in com.google.bitcoin.core
A BitCoin address is fundamentally derived from an elliptic curve public key and a set of network parameters.
Address(NetworkParameters, byte[]) - +Constructor for class com.google.bitcoin.core.Address +
Construct an address from parameters and the hash160 form. +
Address(NetworkParameters, String) - +Constructor for class com.google.bitcoin.core.Address +
Construct an address from parameters and the standard "human readable" form. +
AddressFormatException - Exception in com.google.bitcoin.core
 
AddressFormatException() - +Constructor for exception com.google.bitcoin.core.AddressFormatException +
  +
AddressFormatException(String) - +Constructor for exception com.google.bitcoin.core.AddressFormatException +
  +
addressHeader - +Variable in class com.google.bitcoin.core.NetworkParameters +
First byte of a base58 encoded address. +
AddressMessage - Class in com.google.bitcoin.core
 
addTransaction(Transaction) - +Method in class com.google.bitcoin.core.Block +
Adds a transaction to this block. +
ALLOWED_TIME_DRIFT - +Static variable in class com.google.bitcoin.core.Block +
  +
+
+

+B

+
+
Base58 - Class in com.google.bitcoin.core
A custom form of base58 is used to encode BitCoin addresses.
Base58() - +Constructor for class com.google.bitcoin.core.Base58 +
  +
bitcoinSerialize() - +Method in class com.google.bitcoin.core.GetBlocksMessage +
  +
bitcoinSerialize() - +Method in class com.google.bitcoin.core.GetDataMessage +
  +
bitcoinSerialize() - +Method in class com.google.bitcoin.core.Message +
  +
bitcoinSerializeToStream(OutputStream) - +Method in class com.google.bitcoin.core.InventoryMessage +
  +
bitcoinSerializeToStream(OutputStream) - +Method in class com.google.bitcoin.core.PeerAddress +
  +
bitcoinSerializeToStream(OutputStream) - +Method in class com.google.bitcoin.core.Transaction +
  +
bitcoinSerializeToStream(OutputStream) - +Method in class com.google.bitcoin.core.TransactionInput +
  +
bitcoinSerializeToStream(OutputStream) - +Method in class com.google.bitcoin.core.TransactionOutPoint +
  +
bitcoinSerializeToStream(OutputStream) - +Method in class com.google.bitcoin.core.TransactionOutput +
  +
bitcoinSerializeToStream(OutputStream) - +Method in class com.google.bitcoin.core.VersionMessage +
  +
bitcoinValueToFriendlyString(BigInteger) - +Static method in class com.google.bitcoin.core.Utils +
Returns the given value in nanocoins as a 0.12 type string. +
Block - Class in com.google.bitcoin.core
A block is the foundation of the BitCoin system.
Block(NetworkParameters, byte[]) - +Constructor for class com.google.bitcoin.core.Block +
Constructs a block object from the BitCoin wire format. +
BlockChain - Class in com.google.bitcoin.core
A BlockChain holds a series of Block objects, links them together, and knows how to verify that the + chain follows the rules of the NetworkParameters for this chain.
BlockChain(NetworkParameters, Wallet) - +Constructor for class com.google.bitcoin.core.BlockChain +
  +
broadcastTransaction(Transaction) - +Method in class com.google.bitcoin.core.Peer +
Send the given Transaction, ie, make a payment with BitCoins. +
bytes - +Variable in class com.google.bitcoin.core.Message +
  +
bytesToHexString(byte[]) - +Static method in class com.google.bitcoin.core.Utils +
Returns the given byte array hex encoded. +
+
+

+C

+
+
CENT - +Static variable in class com.google.bitcoin.core.Utils +
How many nanocoins there are in 0.01 BitCoins. +
clientVersion - +Variable in class com.google.bitcoin.core.VersionMessage +
  +
COIN - +Static variable in class com.google.bitcoin.core.Utils +
How many nanocoins there are in a BitCoin. +
com.google.bitcoin.core - package com.google.bitcoin.core
 
com.google.bitcoin.examples - package com.google.bitcoin.examples
 
cursor - +Variable in class com.google.bitcoin.core.Message +
  +
+
+

+D

+
+
decode(String) - +Static method in class com.google.bitcoin.core.Base58 +
  +
decodeToBigInteger(String) - +Static method in class com.google.bitcoin.core.Base58 +
  +
disconnect() - +Method in class com.google.bitcoin.core.Peer +
Terminates the network connection and stops the background thread. +
doubleDigest(byte[]) - +Static method in class com.google.bitcoin.core.Utils +
See Utils.doubleDigest(byte[],int,int). +
doubleDigest(byte[], int, int) - +Static method in class com.google.bitcoin.core.Utils +
Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again. +
doubleDigestTwoBuffers(byte[], int, int, byte[], int, int) - +Static method in class com.google.bitcoin.core.Utils +
Calculates SHA256(SHA256(byte range 1 + byte range 2)). +
+
+

+E

+
+
ECKey - Class in com.google.bitcoin.core
Represents either an elliptic curve keypair that we own and can use for signing transactions.
ECKey() - +Constructor for class com.google.bitcoin.core.ECKey +
Generates an entirely new keypair. +
ECKey(BigInteger) - +Constructor for class com.google.bitcoin.core.ECKey +
Creates an ECKey given only the private key. +
EMPTY_ARRAY - +Static variable in class com.google.bitcoin.core.TransactionInput +
  +
encode(byte[]) - +Static method in class com.google.bitcoin.core.Base58 +
  +
encode() - +Method in class com.google.bitcoin.core.VarInt +
  +
encodeBE() - +Method in class com.google.bitcoin.core.VarInt +
  +
equals(Object) - +Method in class com.google.bitcoin.core.Address +
  +
equals(Object) - +Method in class com.google.bitcoin.core.Block +
  +
equals(Object) - +Method in class com.google.bitcoin.core.Transaction +
  +
+
+

+F

+
+
findKeyFromPubHash(byte[]) - +Method in class com.google.bitcoin.core.Wallet +
Locates a keypair from the keychain given the hash of the public key. +
fromASN1(byte[]) - +Static method in class com.google.bitcoin.core.ECKey +
Construct an ECKey from an ASN.1 encoded private key. +
+
+

+G

+
+
genesisBlock - +Variable in class com.google.bitcoin.core.NetworkParameters +
Genesis block for this chain +
getBalance() - +Method in class com.google.bitcoin.core.Wallet +
Returns the balance of this wallet in nanocoins by summing up all unspent outputs that were sent to us. +
getBlock(byte[]) - +Method in class com.google.bitcoin.core.Peer +
Asks the connected peer for the block of the given hash, and returns a Future representing the answer. +
GetBlocksMessage - Class in com.google.bitcoin.core
 
GetBlocksMessage(NetworkParameters, List<byte[]>, byte[]) - +Constructor for class com.google.bitcoin.core.GetBlocksMessage +
  +
GetDataMessage - Class in com.google.bitcoin.core
 
GetDataMessage(NetworkParameters, byte[]) - +Constructor for class com.google.bitcoin.core.GetDataMessage +
  +
getFromAddress() - +Method in class com.google.bitcoin.core.Script +
Convenience wrapper around getPubKey. +
getFromAddress() - +Method in class com.google.bitcoin.core.TransactionInput +
Convenience method that returns the from address of this input by parsing the scriptSig. +
getHash() - +Method in class com.google.bitcoin.core.Block +
Returns the hash of the block (which for a valid, solved block should be below the target). +
getHash() - +Method in class com.google.bitcoin.core.Transaction +
Returns the transaction hash as you see them in the block explorer. +
getHash160() - +Method in class com.google.bitcoin.core.Address +
The (big endian) 20 byte hash that is the core of a BitCoin address. +
getHashAsString() - +Method in class com.google.bitcoin.core.Block +
Returns the hash of the block (which for a valid, solved block should be below the target) in the form seen + on the block explorer. +
getHashAsString() - +Method in class com.google.bitcoin.core.Transaction +
  +
getInputs() - +Method in class com.google.bitcoin.core.Transaction +
Returns a read-only list of the inputs of this transaction. +
getMerkleRoot() - +Method in class com.google.bitcoin.core.Block +
Returns the merkle root in big endian form, calculating it from transactions if necessary. +
getPubKey() - +Method in class com.google.bitcoin.core.ECKey +
Gets the raw public key value. +
getPubKey() - +Method in class com.google.bitcoin.core.Script +
If a program has two data buffers (constants) and nothing else, the second one is returned. +
getPubKeyHash() - +Method in class com.google.bitcoin.core.ECKey +
Gets the hash160 form of the public key (as seen in addresses). +
getPubKeyHash() - +Method in class com.google.bitcoin.core.Script +
If a program matches the standard template DUP HASH160 EQUALVERIFY CHECKSIG + then this function retrieves the third element, otherwise it throws a ScriptException. +
getScriptBytes() - +Method in class com.google.bitcoin.core.TransactionOutput +
  +
getScriptPubKey() - +Method in class com.google.bitcoin.core.TransactionOutput +
  +
getScriptSig() - +Method in class com.google.bitcoin.core.TransactionInput +
Returns the input script. +
getSizeInBytes() - +Method in class com.google.bitcoin.core.VarInt +
  +
getToAddress() - +Method in class com.google.bitcoin.core.Script +
Gets the destination address from this script, if it's in the required form (see getPubKey). +
getTopBlock() - +Method in class com.google.bitcoin.core.BlockChain +
Returns the highest known block or null if the chain is empty (top block is genesis). +
getUnconnectedBlock() - +Method in class com.google.bitcoin.core.BlockChain +
Returns the most recent unconnected block or null if there are none. +
getValue() - +Method in class com.google.bitcoin.core.TransactionOutput +
Returns the value of this output in nanocoins. +
getValueSentToMe(Wallet) - +Method in class com.google.bitcoin.core.Transaction +
Returns the sum of the outputs that are sending coins to a key in our wallet. +
+
+

+H

+
+
hash - +Variable in class com.google.bitcoin.core.InventoryItem +
  +
hashCode() - +Method in class com.google.bitcoin.core.Address +
  +
hashCode() - +Method in class com.google.bitcoin.core.Block +
  +
hashCode() - +Method in class com.google.bitcoin.core.Transaction +
  +
+
+

+I

+
+
InventoryItem - Class in com.google.bitcoin.core
 
InventoryItem(InventoryItem.Type, byte[]) - +Constructor for class com.google.bitcoin.core.InventoryItem +
  +
InventoryItem.Type - Enum in com.google.bitcoin.core
 
InventoryMessage - Class in com.google.bitcoin.core
 
InventoryMessage(NetworkParameters, byte[]) - +Constructor for class com.google.bitcoin.core.InventoryMessage +
  +
InventoryMessage(NetworkParameters) - +Constructor for class com.google.bitcoin.core.InventoryMessage +
  +
isCoinBase() - +Method in class com.google.bitcoin.core.Transaction +
A coinbase transaction is one that creates a new coin. +
isCoinBase() - +Method in class com.google.bitcoin.core.TransactionInput +
Coinbase transactions have special inputs with hashes of zero. +
isLessThanUnsigned(long, long) - +Static method in class com.google.bitcoin.core.Utils +
Work around lack of unsigned types in Java. +
isMine(Wallet) - +Method in class com.google.bitcoin.core.TransactionOutput +
Returns true if this output is to an address we have the keys for in the wallet. +
isSentToIP() - +Method in class com.google.bitcoin.core.Script +
Returns true if this transaction is of a format that means it was a direct IP to IP transaction. +
isTransactionPresent(Transaction) - +Method in class com.google.bitcoin.core.Wallet +
Returns true if the given transaction is present in the wallet, comparing by hash value (not by object + reference). +
items - +Variable in class com.google.bitcoin.core.InventoryMessage +
  +
+
+

+J

+
+
join(Script, Script) - +Static method in class com.google.bitcoin.core.Script +
Concatenates two scripts to form a new one. +
+
+

+K

+
+
keychain - +Variable in class com.google.bitcoin.core.Wallet +
  +
+
+

+L

+
+
loadFromFile(File) - +Static method in class com.google.bitcoin.core.Wallet +
Returns a wallet deserialized from the given file. +
localServices - +Variable in class com.google.bitcoin.core.VersionMessage +
  +
+
+

+M

+
+
main(String[]) - +Static method in class com.google.bitcoin.examples.PingService +
  +
main(String[]) - +Static method in class com.google.bitcoin.examples.PrivateKeys +
  +
MAX_SIZE - +Static variable in class com.google.bitcoin.core.Message +
  +
Message - Class in com.google.bitcoin.core
A Message is a data structure that can be serialized/deserialized using both the BitCoin proprietary serialization + format and built-in Java object serialization.
Message() - +Constructor for class com.google.bitcoin.core.Message +
This exists for the Java serialization framework to use only. +
+
+

+N

+
+
NetworkConnection - Class in com.google.bitcoin.core
A NetworkConnection handles talking to a remote BitCoin peer at a low level.
NetworkConnection(InetAddress, NetworkParameters) - +Constructor for class com.google.bitcoin.core.NetworkConnection +
Connect to the given IP address using the port specified as part of the network parameters. +
NetworkParameters - Class in com.google.bitcoin.core
NetworkParameters contains the data needed for working with an instantiation of a BitCoin chain.
NetworkParameters() - +Constructor for class com.google.bitcoin.core.NetworkParameters +
  +
+
+

+O

+
+
offset - +Variable in class com.google.bitcoin.core.Message +
  +
onCoinsReceived(Wallet, Transaction, BigInteger, BigInteger) - +Method in interface com.google.bitcoin.core.WalletEventListener +
This is called on a Peer thread when a block is received that sends some coins to you. +
OP_CHECKSIG - +Static variable in class com.google.bitcoin.core.Script +
  +
OP_DUP - +Static variable in class com.google.bitcoin.core.Script +
  +
OP_EQUALVERIFY - +Static variable in class com.google.bitcoin.core.Script +
  +
OP_HASH160 - +Static variable in class com.google.bitcoin.core.Script +
  +
OP_PUSHDATA1 - +Static variable in class com.google.bitcoin.core.Script +
  +
OP_PUSHDATA2 - +Static variable in class com.google.bitcoin.core.Script +
  +
OP_PUSHDATA4 - +Static variable in class com.google.bitcoin.core.Script +
  +
+
+

+P

+
+
packetMagic - +Variable in class com.google.bitcoin.core.NetworkParameters +
The header bytes that identify the start of a packet on this network. +
params - +Variable in class com.google.bitcoin.core.Message +
  +
parse() - +Method in class com.google.bitcoin.core.GetBlocksMessage +
  +
parse() - +Method in class com.google.bitcoin.core.GetDataMessage +
  +
parse() - +Method in class com.google.bitcoin.core.InventoryMessage +
  +
parse() - +Method in class com.google.bitcoin.core.PeerAddress +
  +
parse() - +Method in class com.google.bitcoin.core.UnknownMessage +
  +
parse() - +Method in class com.google.bitcoin.core.VersionMessage +
  +
Peer - Class in com.google.bitcoin.core
A Peer handles the high level communication with a BitCoin node.
Peer(NetworkParameters, NetworkConnection, BlockChain) - +Constructor for class com.google.bitcoin.core.Peer +
Construct a peer that handles the given network connection and reads/writes from the given block chain. +
PeerAddress - Class in com.google.bitcoin.core
A PeerAddress holds an IP address and port number representing the network location of + a peer in the BitCoin P2P network.
PeerAddress(NetworkParameters, byte[], int) - +Constructor for class com.google.bitcoin.core.PeerAddress +
  +
PeerAddress(InetAddress, int) - +Constructor for class com.google.bitcoin.core.PeerAddress +
  +
ping() - +Method in class com.google.bitcoin.core.NetworkConnection +
Sends a "ping" message to the remote node. +
PingService - Class in com.google.bitcoin.examples
PingService demonstrates basic usage of the library.
PingService() - +Constructor for class com.google.bitcoin.examples.PingService +
  +
port - +Variable in class com.google.bitcoin.core.NetworkParameters +
Default TCP port on which to connect to nodes. +
PrivateKeys - Class in com.google.bitcoin.examples
This example shows how to solve the challenge Hal posted here: + + http://www.bitcoin.org/smf/index.php?topic=3638.0 + + in which a private key with some coins associated with it is published.
PrivateKeys() - +Constructor for class com.google.bitcoin.examples.PrivateKeys +
  +
prodNet() - +Static method in class com.google.bitcoin.core.NetworkParameters +
The primary BitCoin chain created by Satoshi. +
proofOfWorkLimit - +Variable in class com.google.bitcoin.core.NetworkParameters +
What the easiest allowable proof of work should be. +
PROTOCOL_VERSION - +Static variable in class com.google.bitcoin.core.VersionMessage +
  +
ProtocolException - Exception in com.google.bitcoin.core
 
ProtocolException(String) - +Constructor for exception com.google.bitcoin.core.ProtocolException +
  +
ProtocolException(Exception) - +Constructor for exception com.google.bitcoin.core.ProtocolException +
  +
ProtocolException(String, Exception) - +Constructor for exception com.google.bitcoin.core.ProtocolException +
  +
+
+

+R

+
+
readMessage() - +Method in class com.google.bitcoin.core.NetworkConnection +
Reads a network message from the wire, blocking until the message is fully received. +
readUint32(byte[], int) - +Static method in class com.google.bitcoin.core.Utils +
  +
readUint32BE(byte[], int) - +Static method in class com.google.bitcoin.core.Utils +
  +
reverseBytes(byte[]) - +Static method in class com.google.bitcoin.core.Utils +
Returns a copy of the given byte array in reverse order. +
run(Transaction) - +Method in class com.google.bitcoin.core.Script +
Runs the script with the given Transaction as the "context". +
+
+

+S

+
+
saveToFile(File) - +Method in class com.google.bitcoin.core.Wallet +
Uses Java serialization to save the wallet to the given file. +
Script - Class in com.google.bitcoin.core
BitCoin transactions don't specify what they do directly.
Script(NetworkParameters, byte[], int, int) - +Constructor for class com.google.bitcoin.core.Script +
Construct a Script using the given network parameters and a range of the programBytes array. +
ScriptException - Exception in com.google.bitcoin.core
 
ScriptException(String) - +Constructor for exception com.google.bitcoin.core.ScriptException +
  +
ScriptException(String, Exception) - +Constructor for exception com.google.bitcoin.core.ScriptException +
  +
sendCoins(Peer, Address, BigInteger) - +Method in class com.google.bitcoin.core.Wallet +
Sends coins to the given address, via the given Peer. +
setTracing(boolean) - +Method in class com.google.bitcoin.core.Script +
If true, running a program will log its instructions. +
sha256hash160(byte[]) - +Static method in class com.google.bitcoin.core.Utils +
Calculates RIPEMD160(SHA256(input)). +
shutdown() - +Method in class com.google.bitcoin.core.NetworkConnection +
Shuts down the network socket. +
sign(byte[]) - +Method in class com.google.bitcoin.core.ECKey +
Calcuates an ECDSA signature in DER format for the given input hash. +
signInputs(Transaction.SigHash, Wallet) - +Method in class com.google.bitcoin.core.Transaction +
Once a transaction has some inputs and outputs added, the signatures in the inputs can be calculated. +
start() - +Method in class com.google.bitcoin.core.Peer +
Starts the background thread that processes messages. +
startBlockChainDownload() - +Method in class com.google.bitcoin.core.Peer +
Starts an asynchronous download of the block chain. +
+
+

+T

+
+
testNet() - +Static method in class com.google.bitcoin.core.NetworkParameters +
The test chain created by Gavin. +
time - +Variable in class com.google.bitcoin.core.VersionMessage +
  +
toAddress(NetworkParameters) - +Method in class com.google.bitcoin.core.ECKey +
  +
toNanoCoins(int, int) - +Static method in class com.google.bitcoin.core.Utils +
Convert an amount expressed in the way humans are used to into nanocoins. +
toString() - +Method in class com.google.bitcoin.core.Address +
  +
toString() - +Method in class com.google.bitcoin.core.AddressMessage +
  +
toString() - +Method in class com.google.bitcoin.core.Block +
Returns a multi-line string containing a description of the contents of the block. +
toString() - +Method in class com.google.bitcoin.core.ECKey +
  +
toString() - +Method in class com.google.bitcoin.core.GetBlocksMessage +
  +
toString() - +Method in class com.google.bitcoin.core.InventoryItem +
  +
toString() - +Method in class com.google.bitcoin.core.PeerAddress +
  +
toString() - +Method in class com.google.bitcoin.core.Script +
Returns the program opcodes as a string, for example "[1234] DUP HAHS160" +
toString() - +Method in class com.google.bitcoin.core.Transaction +
  +
toString() - +Method in class com.google.bitcoin.core.TransactionInput +
Returns a human readable debug string. +
toString() - +Method in class com.google.bitcoin.core.TransactionOutput +
Returns a human readable debug string. +
toString() - +Method in class com.google.bitcoin.core.UnknownMessage +
  +
Transaction - Class in com.google.bitcoin.core
A transaction represents the movement of coins from some addresses to some other addresses.
Transaction(NetworkParameters, byte[]) - +Constructor for class com.google.bitcoin.core.Transaction +
Creates a transaction from the given serialized bytes, eg, from a block or a tx network message. +
Transaction(NetworkParameters, byte[], int) - +Constructor for class com.google.bitcoin.core.Transaction +
Creates a transaction by reading payload starting from offset bytes in. +
Transaction.SigHash - Enum in com.google.bitcoin.core
These constants are a part of a scriptSig signature on the inputs.
TransactionInput - Class in com.google.bitcoin.core
A transfer of coins from one address to another creates a transaction in which the outputs + can be claimed by the recipient in the input of another transaction.
TransactionInput(NetworkParameters, byte[], int) - +Constructor for class com.google.bitcoin.core.TransactionInput +
Deserializes an input message. +
TransactionOutPoint - Class in com.google.bitcoin.core
This message is effectively a reference or pointer to a transaction output.
TransactionOutPoint(NetworkParameters, byte[], int) - +Constructor for class com.google.bitcoin.core.TransactionOutPoint +
Deserializes the message. +
TransactionOutput - Class in com.google.bitcoin.core
A TransactionOutput message contains a scriptPubKey that controls who is able to spend its value.
TransactionOutput(NetworkParameters, Transaction, byte[], int) - +Constructor for class com.google.bitcoin.core.TransactionOutput +
Deserializes a transaction output message. +
type - +Variable in class com.google.bitcoin.core.InventoryItem +
  +
+
+

+U

+
+
uint32ToByteArrayBE(long, byte[], int) - +Static method in class com.google.bitcoin.core.Utils +
  +
uint32ToByteArrayLE(long, byte[], int) - +Static method in class com.google.bitcoin.core.Utils +
  +
uint32ToByteStreamLE(long, OutputStream) - +Static method in class com.google.bitcoin.core.Utils +
  +
uint64ToByteStreamLE(BigInteger, OutputStream) - +Static method in class com.google.bitcoin.core.Utils +
  +
UnknownMessage - Class in com.google.bitcoin.core
 
UnknownMessage(NetworkParameters, String, byte[]) - +Constructor for class com.google.bitcoin.core.UnknownMessage +
  +
unspent - +Variable in class com.google.bitcoin.core.Wallet +
  +
Utils - Class in com.google.bitcoin.core
A collection of various utility methods that are helpful for working with the BitCoin protocol.
Utils() - +Constructor for class com.google.bitcoin.core.Utils +
  +
+
+

+V

+
+
value - +Variable in class com.google.bitcoin.core.VarInt +
  +
valueOf(String) - +Static method in enum com.google.bitcoin.core.InventoryItem.Type +
Returns the enum constant of this type with the specified name. +
valueOf(String) - +Static method in enum com.google.bitcoin.core.Transaction.SigHash +
Returns the enum constant of this type with the specified name. +
values() - +Static method in enum com.google.bitcoin.core.InventoryItem.Type +
Returns an array containing the constants of this enum type, in +the order they are declared. +
values() - +Static method in enum com.google.bitcoin.core.Transaction.SigHash +
Returns an array containing the constants of this enum type, in +the order they are declared. +
VarInt - Class in com.google.bitcoin.core
 
VarInt(long) - +Constructor for class com.google.bitcoin.core.VarInt +
  +
VarInt(byte[], int) - +Constructor for class com.google.bitcoin.core.VarInt +
  +
VerificationException - Exception in com.google.bitcoin.core
 
VerificationException(String) - +Constructor for exception com.google.bitcoin.core.VerificationException +
  +
verify() - +Method in class com.google.bitcoin.core.Block +
Checks the block data to ensure it follows the rules laid out in the network parameters. +
verify(byte[], byte[], byte[]) - +Static method in class com.google.bitcoin.core.ECKey +
Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. +
verify(byte[], byte[]) - +Method in class com.google.bitcoin.core.ECKey +
Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. +
verifyInput(int, Transaction) - +Method in class com.google.bitcoin.core.Transaction +
Given a named input and the transaction output it connects to, runs the script formed from the + concatenation of the input and output scripts, returning true if the link is valid. +
VersionMessage - Class in com.google.bitcoin.core
 
VersionMessage(NetworkParameters, byte[]) - +Constructor for class com.google.bitcoin.core.VersionMessage +
  +
VersionMessage(NetworkParameters) - +Constructor for class com.google.bitcoin.core.VersionMessage +
  +
+
+

+W

+
+
Wallet - Class in com.google.bitcoin.core
A Wallet stores keys and a record of transactions that have not yet been spent.
Wallet(NetworkParameters) - +Constructor for class com.google.bitcoin.core.Wallet +
Creates a new, empty wallet with no keys and no transactions. +
WalletEventListener - Interface in com.google.bitcoin.core
Implementing WalletEventListener allows you to learn when a wallets balance has changed.
writeMessage(String, Message) - +Method in class com.google.bitcoin.core.NetworkConnection +
Writes the given message out over the network using the protocol tag. +
+
+A B C D E F G H I J K L M N O P R S T U V W + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 000000000..8c99a8c62 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,39 @@ + + + + + + +Generated Documentation (Untitled) + + + + + + + + + + + +<H2> +Frame Alert</H2> + +<P> +This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client. +<BR> +Link to<A HREF="overview-summary.html">Non-frame version.</A> + + + diff --git a/docs/overview-frame.html b/docs/overview-frame.html new file mode 100644 index 000000000..c10f604bf --- /dev/null +++ b/docs/overview-frame.html @@ -0,0 +1,44 @@ + + + + + + +Overview List + + + + + + + + + + + + + + + +
+
+ + + + + +
All Classes +

+ +Packages +
+com.google.bitcoin.core +
+com.google.bitcoin.examples +
+

+ +

+  + + diff --git a/docs/overview-summary.html b/docs/overview-summary.html new file mode 100644 index 000000000..8d56fcca7 --- /dev/null +++ b/docs/overview-summary.html @@ -0,0 +1,153 @@ + + + + + + +Overview + + + + + + + + + + + + +


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + + + + + + + + + + + +
+Packages
com.google.bitcoin.core 
com.google.bitcoin.examples 
+ +


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/overview-tree.html b/docs/overview-tree.html new file mode 100644 index 000000000..2fbb149ec --- /dev/null +++ b/docs/overview-tree.html @@ -0,0 +1,180 @@ + + + + + + +Class Hierarchy + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+Hierarchy For All Packages

+
+
+
Package Hierarchies:
com.google.bitcoin.core, com.google.bitcoin.examples
+
+

+Class Hierarchy +

+ +

+Interface Hierarchy +

+ +

+Enum Hierarchy +

+ +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/package-list b/docs/package-list new file mode 100644 index 000000000..539935184 --- /dev/null +++ b/docs/package-list @@ -0,0 +1,2 @@ +com.google.bitcoin.core +com.google.bitcoin.examples diff --git a/docs/resources/inherit.gif b/docs/resources/inherit.gif new file mode 100644 index 0000000000000000000000000000000000000000..c814867a13deb0ca7ea2156c6ca1d5a03372af7e GIT binary patch literal 57 zcmZ?wbhEHbIIT!9-C*e{wE9>Kx3D)-;0v)C; KYxQGgum%9JOA&7X literal 0 HcmV?d00001 diff --git a/docs/serialized-form.html b/docs/serialized-form.html new file mode 100644 index 000000000..5cb15c8a0 --- /dev/null +++ b/docs/serialized-form.html @@ -0,0 +1,943 @@ + + + + + + +Serialized Form + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+Serialized Form

+
+
+ + + + + +
+Package com.google.bitcoin.core
+ +

+ + + + + +
+Class com.google.bitcoin.core.AddressFormatException extends java.lang.Exception implements Serializable
+ +

+ +

+ + + + + +
+Class com.google.bitcoin.core.AddressMessage extends Message implements Serializable
+ +

+ + + + + +
+Serialized Fields
+ +

+addresses

+
+java.util.List<E> addresses
+
+
+
+
+ +

+ + + + + +
+Class com.google.bitcoin.core.Block extends Message implements Serializable
+ +

+ + + + + +
+Serialized Fields
+ +

+version

+
+long version
+
+
+
+
+
+

+prevBlockHash

+
+byte[] prevBlockHash
+
+
+
+
+
+

+merkleRoot

+
+byte[] merkleRoot
+
+
+
+
+
+

+time

+
+long time
+
+
+
+
+
+

+difficultyTarget

+
+long difficultyTarget
+
+
+
+
+
+

+nonce

+
+long nonce
+
+
+
+
+
+

+transactions

+
+java.util.List<E> transactions
+
+
+
+
+
+

+hash

+
+byte[] hash
+
+
+
+
+
+

+prevBlock

+
+Block prevBlock
+
+
+
+
+ +

+ + + + + +
+Class com.google.bitcoin.core.ECKey extends java.lang.Object implements Serializable
+ +

+serialVersionUID: -728224901792295832L + +

+ + + + + +
+Serialized Fields
+ +

+priv

+
+java.math.BigInteger priv
+
+
+
+
+
+

+pub

+
+byte[] pub
+
+
+
+
+ +

+ + + + + +
+Class com.google.bitcoin.core.GetBlocksMessage extends Message implements Serializable
+ +

+ + + + + +
+Serialized Fields
+ +

+locator

+
+java.util.List<E> locator
+
+
+
+
+
+

+stopHash

+
+byte[] stopHash
+
+
+
+
+ +

+ + + + + +
+Class com.google.bitcoin.core.GetDataMessage extends Message implements Serializable
+ +

+ +

+ + + + + +
+Class com.google.bitcoin.core.InventoryMessage extends Message implements Serializable
+ +

+ + + + + +
+Serialized Fields
+ +

+items

+
+java.util.List<E> items
+
+
+
+
+ +

+ + + + + +
+Class com.google.bitcoin.core.Message extends java.lang.Object implements Serializable
+ +

+ + + + + +
+Serialized Fields
+ +

+params

+
+NetworkParameters params
+
+
+
+
+ +

+ + + + + +
+Class com.google.bitcoin.core.NetworkParameters extends java.lang.Object implements Serializable
+ +

+serialVersionUID: 2579833727976661964L + +

+ + + + + +
+Serialized Fields
+ +

+genesisBlock

+
+Block genesisBlock
+
+
Genesis block for this chain +

+

+
+
+
+

+proofOfWorkLimit

+
+java.math.BigInteger proofOfWorkLimit
+
+
What the easiest allowable proof of work should be. +

+

+
+
+
+

+port

+
+int port
+
+
Default TCP port on which to connect to nodes. +

+

+
+
+
+

+packetMagic

+
+long packetMagic
+
+
The header bytes that identify the start of a packet on this network. +

+

+
+
+
+

+addressHeader

+
+byte addressHeader
+
+
First byte of a base58 encoded address. +

+

+
+
+ +

+ + + + + +
+Class com.google.bitcoin.core.PeerAddress extends Message implements Serializable
+ +

+serialVersionUID: 7501293709324197411L + +

+ + + + + +
+Serialized Fields
+ +

+addr

+
+java.net.InetAddress addr
+
+
+
+
+
+

+port

+
+int port
+
+
+
+
+
+

+services

+
+java.math.BigInteger services
+
+
+
+
+ +

+ + + + + +
+Class com.google.bitcoin.core.ProtocolException extends java.lang.Exception implements Serializable
+ +

+ +

+ + + + + +
+Class com.google.bitcoin.core.ScriptException extends java.lang.Exception implements Serializable
+ +

+ +

+ + + + + +
+Class com.google.bitcoin.core.Transaction extends Message implements Serializable
+ +

+serialVersionUID: -8567546957352643140L + +

+ + + + + +
+Serialized Fields
+ +

+version

+
+long version
+
+
+
+
+
+

+inputs

+
+java.util.ArrayList<E> inputs
+
+
+
+
+
+

+outputs

+
+java.util.ArrayList<E> outputs
+
+
+
+
+
+

+lockTime

+
+long lockTime
+
+
+
+
+ +

+ + + + + +
+Class com.google.bitcoin.core.TransactionInput extends Message implements Serializable
+ +

+serialVersionUID: -7687665228438202968L + +

+ + + + + +
+Serialized Fields
+ +

+sequence

+
+long sequence
+
+
+
+
+
+

+outpoint

+
+TransactionOutPoint outpoint
+
+
+
+
+
+

+scriptBytes

+
+byte[] scriptBytes
+
+
+
+
+ +

+ + + + + +
+Class com.google.bitcoin.core.TransactionOutPoint extends Message implements Serializable
+ +

+ + + + + +
+Serialized Fields
+ +

+hash

+
+byte[] hash
+
+
+
+
+
+

+index

+
+long index
+
+
+
+
+
+

+fromTx

+
+Transaction fromTx
+
+
+
+
+ +

+ + + + + +
+Class com.google.bitcoin.core.TransactionOutput extends Message implements Serializable
+ +

+serialVersionUID: -590332479859256824L + +

+ + + + + +
+Serialized Fields
+ +

+value

+
+java.math.BigInteger value
+
+
+
+
+
+

+scriptBytes

+
+byte[] scriptBytes
+
+
+
+
+
+

+isSpent

+
+boolean isSpent
+
+
+
+
+
+

+parentTransaction

+
+Transaction parentTransaction
+
+
+
+
+ +

+ + + + + +
+Class com.google.bitcoin.core.UnknownMessage extends Message implements Serializable
+ +

+ + + + + +
+Serialized Fields
+ +

+name

+
+java.lang.String name
+
+
+
+
+ +

+ + + + + +
+Class com.google.bitcoin.core.VerificationException extends java.lang.Exception implements Serializable
+ +

+ +

+ + + + + +
+Class com.google.bitcoin.core.VersionMessage extends Message implements Serializable
+ +

+ + + + + +
+Serialized Fields
+ +

+clientVersion

+
+int clientVersion
+
+
+
+
+
+

+localServices

+
+int localServices
+
+
+
+
+
+

+time

+
+java.math.BigInteger time
+
+
+
+
+ +

+ + + + + +
+Class com.google.bitcoin.core.Wallet extends java.lang.Object implements Serializable
+ +

+serialVersionUID: -4501424466753895784L + +

+ + + + + +
+Serialization Methods
+ +

+

+readObject

+
+private void readObject(java.io.ObjectInputStream in)
+                 throws java.io.IOException,
+                        java.lang.ClassNotFoundException
+
+
+ +
Throws: +
java.io.IOException +
java.lang.ClassNotFoundException
+
+
+ + + + + +
+Serialized Fields
+ +

+unspent

+
+java.util.ArrayList<E> unspent
+
+
+
+
+
+

+fullySpent

+
+java.util.LinkedList<E> fullySpent
+
+
+
+
+
+

+keychain

+
+java.util.ArrayList<E> keychain
+
+
+
+
+
+

+params

+
+NetworkParameters params
+
+
+
+
+ +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/docs/stylesheet.css b/docs/stylesheet.css new file mode 100644 index 000000000..6ea9e5161 --- /dev/null +++ b/docs/stylesheet.css @@ -0,0 +1,29 @@ +/* Javadoc style sheet */ + +/* Define colors, fonts and other style attributes here to override the defaults */ + +/* Page background color */ +body { background-color: #FFFFFF; color:#000000 } + +/* Headings */ +h1 { font-size: 145% } + +/* Table colors */ +.TableHeadingColor { background: #CCCCFF; color:#000000 } /* Dark mauve */ +.TableSubHeadingColor { background: #EEEEFF; color:#000000 } /* Light mauve */ +.TableRowColor { background: #FFFFFF; color:#000000 } /* White */ + +/* Font used in left-hand frame lists */ +.FrameTitleFont { font-size: 100%; font-family: Helvetica, Arial, sans-serif; color:#000000 } +.FrameHeadingFont { font-size: 90%; font-family: Helvetica, Arial, sans-serif; color:#000000 } +.FrameItemFont { font-size: 90%; font-family: Helvetica, Arial, sans-serif; color:#000000 } + +/* Navigation bar fonts and colors */ +.NavBarCell1 { background-color:#EEEEFF; color:#000000} /* Light mauve */ +.NavBarCell1Rev { background-color:#00008B; color:#FFFFFF} /* Dark Blue */ +.NavBarFont1 { font-family: Arial, Helvetica, sans-serif; color:#000000;color:#000000;} +.NavBarFont1Rev { font-family: Arial, Helvetica, sans-serif; color:#FFFFFF;color:#FFFFFF;} + +.NavBarCell2 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF; color:#000000} +.NavBarCell3 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF; color:#000000} + diff --git a/lib/junit-4.8.2.jar b/lib/junit-4.8.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..5b4bb849af9583fec1ea0a0ccc0d571ef49aa8ba GIT binary patch literal 237344 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 + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +public class LICENSE +{ + public static String licenseText = + "Copyright (c) 2000-2008 The Legion Of The Bouncy Castle (http://www.bouncycastle.org) " + + System.getProperty("line.separator") + + System.getProperty("line.separator") + + "Permission is hereby granted, free of charge, to any person obtaining a copy of this software " + + System.getProperty("line.separator") + + "and associated documentation files (the \"Software\"), to deal in the Software without restriction, " + + System.getProperty("line.separator") + + "including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, " + + System.getProperty("line.separator") + + "and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so," + + System.getProperty("line.separator") + + "subject to the following conditions:" + + System.getProperty("line.separator") + + System.getProperty("line.separator") + + "The above copyright notice and this permission notice shall be included in all copies or substantial" + + System.getProperty("line.separator") + + "portions of the Software." + + System.getProperty("line.separator") + + System.getProperty("line.separator") + + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED," + + System.getProperty("line.separator") + + "INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR" + + System.getProperty("line.separator") + + "PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE" + + System.getProperty("line.separator") + + "LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR" + + System.getProperty("line.separator") + + "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER" + + System.getProperty("line.separator") + + "DEALINGS IN THE SOFTWARE."; + + public static void main( + String[] args) + { + System.out.println(licenseText); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1ApplicationSpecificParser.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1ApplicationSpecificParser.java new file mode 100644 index 000000000..f08802702 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1ApplicationSpecificParser.java @@ -0,0 +1,10 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +public interface ASN1ApplicationSpecificParser + extends DEREncodable +{ + DEREncodable readObject() + throws IOException; +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1Choice.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1Choice.java new file mode 100644 index 000000000..881b6d4d3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1Choice.java @@ -0,0 +1,14 @@ +package com.google.bitcoin.bouncycastle.asn1; + +/** + * Marker interface for CHOICE objects - if you implement this in a role your + * own object any attempt to tag the object implicitly will convert the tag to + * an explicit one as the encoding rules require. + *

+ * If you use this interface your class should also implement the getInstance + * pattern which takes a tag object and the tagging mode used. + */ +public interface ASN1Choice +{ + // marker interface +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1Encodable.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1Encodable.java new file mode 100644 index 000000000..0fafdc67a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1Encodable.java @@ -0,0 +1,102 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Base class for objects which can be written directly to ASN.1 output streams. + */ +public abstract class ASN1Encodable + implements DEREncodable +{ + public static final String DER = "DER"; + public static final String BER = "BER"; + + /** + * Return the default BER or DER encoding for this object. + * + * @return BER/DER byte encoded object. + * @throws IOException on encoding error. + */ + public byte[] getEncoded() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ASN1OutputStream aOut = new ASN1OutputStream(bOut); + + aOut.writeObject(this); + + return bOut.toByteArray(); + } + + /** + * Return either the default for "BER" or a DER encoding if "DER" is specified. + * + * @param encoding name of encoding to use. + * @return byte encoded object. + * @throws IOException on encoding error. + */ + public byte[] getEncoded( + String encoding) + throws IOException + { + if (encoding.equals(DER)) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + DEROutputStream dOut = new DEROutputStream(bOut); + + dOut.writeObject(this); + + return bOut.toByteArray(); + } + + return this.getEncoded(); + } + + /** + * Return the DER encoding of the object, null if the DER encoding can not be made. + * + * @return a DER byte array, null otherwise. + */ + public byte[] getDEREncoded() + { + try + { + return this.getEncoded(DER); + } + catch (IOException e) + { + return null; + } + } + + public int hashCode() + { + return this.toASN1Object().hashCode(); + } + + public boolean equals( + Object o) + { + if (this == o) + { + return true; + } + + if (!(o instanceof DEREncodable)) + { + return false; + } + + DEREncodable other = (DEREncodable)o; + + return this.toASN1Object().equals(other.getDERObject()); + } + + public DERObject getDERObject() + { + return this.toASN1Object(); + } + + public abstract DERObject toASN1Object(); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1EncodableVector.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1EncodableVector.java new file mode 100644 index 000000000..374d324bf --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1EncodableVector.java @@ -0,0 +1,14 @@ +package com.google.bitcoin.bouncycastle.asn1; + +/** + * the parent class for this will eventually disappear. Use this one! + */ +public class ASN1EncodableVector + extends DEREncodableVector +{ + // migrating from DEREncodeableVector + public ASN1EncodableVector() + { + + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1Generator.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1Generator.java new file mode 100644 index 000000000..f0355858b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1Generator.java @@ -0,0 +1,15 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.OutputStream; + +public abstract class ASN1Generator +{ + protected OutputStream _out; + + public ASN1Generator(OutputStream out) + { + _out = out; + } + + public abstract OutputStream getRawOutputStream(); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1InputStream.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1InputStream.java new file mode 100644 index 000000000..5a54b25ff --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1InputStream.java @@ -0,0 +1,386 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import com.google.bitcoin.bouncycastle.util.io.Streams; + +/** + * a general purpose ASN.1 decoder - note: this class differs from the + * others in that it returns null after it has read the last object in + * the stream. If an ASN.1 NULL is encountered a DER/BER Null object is + * returned. + */ +public class ASN1InputStream + extends FilterInputStream + implements DERTags +{ + private final int limit; + private final boolean lazyEvaluate; + + public ASN1InputStream( + InputStream is) + { + this(is, Integer.MAX_VALUE); + } + + /** + * Create an ASN1InputStream based on the input byte array. The length of DER objects in + * the stream is automatically limited to the length of the input array. + * + * @param input array containing ASN.1 encoded data. + */ + public ASN1InputStream( + byte[] input) + { + this(new ByteArrayInputStream(input), input.length); + } + + /** + * Create an ASN1InputStream based on the input byte array. The length of DER objects in + * the stream is automatically limited to the length of the input array. + * + * @param input array containing ASN.1 encoded data. + * @param lazyEvaluate true if parsing inside constructed objects can be delayed. + */ + public ASN1InputStream( + byte[] input, + boolean lazyEvaluate) + { + this(new ByteArrayInputStream(input), input.length, lazyEvaluate); + } + + /** + * Create an ASN1InputStream where no DER object will be longer than limit. + * + * @param input stream containing ASN.1 encoded data. + * @param limit maximum size of a DER encoded object. + */ + public ASN1InputStream( + InputStream input, + int limit) + { + this(input, limit, false); + } + + /** + * Create an ASN1InputStream where no DER object will be longer than limit, and constructed + * objects such as sequences will be parsed lazily. + * + * @param input stream containing ASN.1 encoded data. + * @param limit maximum size of a DER encoded object. + * @param lazyEvaluate true if parsing inside constructed objects can be delayed. + */ + public ASN1InputStream( + InputStream input, + int limit, + boolean lazyEvaluate) + { + super(input); + this.limit = limit; + this.lazyEvaluate = lazyEvaluate; + } + + protected int readLength() + throws IOException + { + return readLength(this, limit); + } + + protected void readFully( + byte[] bytes) + throws IOException + { + if (Streams.readFully(this, bytes) != bytes.length) + { + throw new EOFException("EOF encountered in middle of object"); + } + } + + /** + * build an object given its tag and the number of bytes to construct it from. + */ + protected DERObject buildObject( + int tag, + int tagNo, + int length) + throws IOException + { + boolean isConstructed = (tag & CONSTRUCTED) != 0; + + DefiniteLengthInputStream defIn = new DefiniteLengthInputStream(this, length); + + if ((tag & APPLICATION) != 0) + { + return new DERApplicationSpecific(isConstructed, tagNo, defIn.toByteArray()); + } + + if ((tag & TAGGED) != 0) + { + return new BERTaggedObjectParser(tag, tagNo, defIn).getDERObject(); + } + + if (isConstructed) + { + // TODO There are other tags that may be constructed (e.g. BIT_STRING) + switch (tagNo) + { + case OCTET_STRING: + // + // yes, people actually do this... + // + return new BERConstructedOctetString(buildDEREncodableVector(defIn).v); + case SEQUENCE: + if (lazyEvaluate) + { + return new LazyDERSequence(defIn.toByteArray()); + } + else + { + return DERFactory.createSequence(buildDEREncodableVector(defIn)); + } + case SET: + return DERFactory.createSet(buildDEREncodableVector(defIn), false); + case EXTERNAL: + return new DERExternal(buildDEREncodableVector(defIn)); + default: + return new DERUnknownTag(true, tagNo, defIn.toByteArray()); + } + } + + return createPrimitiveDERObject(tagNo, defIn.toByteArray()); + } + + ASN1EncodableVector buildEncodableVector() + throws IOException + { + ASN1EncodableVector v = new ASN1EncodableVector(); + DERObject o; + + while ((o = readObject()) != null) + { + v.add(o); + } + + return v; + } + + ASN1EncodableVector buildDEREncodableVector( + DefiniteLengthInputStream dIn) throws IOException + { + return new ASN1InputStream(dIn).buildEncodableVector(); + } + + public DERObject readObject() + throws IOException + { + int tag = read(); + if (tag <= 0) + { + if (tag == 0) + { + throw new IOException("unexpected end-of-contents marker"); + } + + return null; + } + + // + // calculate tag number + // + int tagNo = readTagNumber(this, tag); + + boolean isConstructed = (tag & CONSTRUCTED) != 0; + + // + // calculate length + // + int length = readLength(); + + if (length < 0) // indefinite length method + { + if (!isConstructed) + { + throw new IOException("indefinite length primitive encoding encountered"); + } + + IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(this); + + if ((tag & APPLICATION) != 0) + { + ASN1StreamParser sp = new ASN1StreamParser(indIn, limit); + + return new BERApplicationSpecificParser(tagNo, sp).getDERObject(); + } + if ((tag & TAGGED) != 0) + { + return new BERTaggedObjectParser(tag, tagNo, indIn).getDERObject(); + } + + ASN1StreamParser sp = new ASN1StreamParser(indIn, limit); + + // TODO There are other tags that may be constructed (e.g. BIT_STRING) + switch (tagNo) + { + case OCTET_STRING: + return new BEROctetStringParser(sp).getDERObject(); + case SEQUENCE: + return new BERSequenceParser(sp).getDERObject(); + case SET: + return new BERSetParser(sp).getDERObject(); + case EXTERNAL: + return new DERExternalParser(sp).getDERObject(); + default: + throw new IOException("unknown BER object encountered"); + } + } + else + { + return buildObject(tag, tagNo, length); + } + } + + static int readTagNumber(InputStream s, int tag) + throws IOException + { + int tagNo = tag & 0x1f; + + // + // with tagged object tag number is bottom 5 bits, or stored at the start of the content + // + if (tagNo == 0x1f) + { + tagNo = 0; + + int b = s.read(); + + // X.690-0207 8.1.2.4.2 + // "c) bits 7 to 1 of the first subsequent octet shall not all be zero." + if ((b & 0x7f) == 0) // Note: -1 will pass + { + throw new IOException("corrupted stream - invalid high tag number found"); + } + + while ((b >= 0) && ((b & 0x80) != 0)) + { + tagNo |= (b & 0x7f); + tagNo <<= 7; + b = s.read(); + } + + if (b < 0) + { + throw new EOFException("EOF found inside tag value."); + } + + tagNo |= (b & 0x7f); + } + + return tagNo; + } + + static int readLength(InputStream s, int limit) + throws IOException + { + int length = s.read(); + if (length < 0) + { + throw new EOFException("EOF found when length expected"); + } + + if (length == 0x80) + { + return -1; // indefinite-length encoding + } + + if (length > 127) + { + int size = length & 0x7f; + + if (size > 4) + { + throw new IOException("DER length more than 4 bytes: " + size); + } + + length = 0; + for (int i = 0; i < size; i++) + { + int next = s.read(); + + if (next < 0) + { + throw new EOFException("EOF found reading length"); + } + + length = (length << 8) + next; + } + + if (length < 0) + { + throw new IOException("corrupted stream - negative length found"); + } + + if (length >= limit) // after all we must have read at least 1 byte + { + throw new IOException("corrupted stream - out of bounds length found"); + } + } + + return length; + } + + static DERObject createPrimitiveDERObject( + int tagNo, + byte[] bytes) + { + switch (tagNo) + { + case BIT_STRING: + { + int padBits = bytes[0]; + byte[] data = new byte[bytes.length - 1]; + System.arraycopy(bytes, 1, data, 0, bytes.length - 1); + return new DERBitString(data, padBits); + } + case BMP_STRING: + return new DERBMPString(bytes); + case BOOLEAN: + return new DERBoolean(bytes); + case ENUMERATED: + return new DEREnumerated(bytes); + case GENERALIZED_TIME: + return new DERGeneralizedTime(bytes); + case GENERAL_STRING: + return new DERGeneralString(bytes); + case IA5_STRING: + return new DERIA5String(bytes); + case INTEGER: + return new DERInteger(bytes); + case NULL: + return DERNull.INSTANCE; // actual content is ignored (enforce 0 length?) + case NUMERIC_STRING: + return new DERNumericString(bytes); + case OBJECT_IDENTIFIER: + return new DERObjectIdentifier(bytes); + case OCTET_STRING: + return new DEROctetString(bytes); + case PRINTABLE_STRING: + return new DERPrintableString(bytes); + case T61_STRING: + return new DERT61String(bytes); + case UNIVERSAL_STRING: + return new DERUniversalString(bytes); + case UTC_TIME: + return new DERUTCTime(bytes); + case UTF8_STRING: + return new DERUTF8String(bytes); + case VISIBLE_STRING: + return new DERVisibleString(bytes); + default: + return new DERUnknownTag(false, tagNo, bytes); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1Null.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1Null.java new file mode 100644 index 000000000..e1e344224 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1Null.java @@ -0,0 +1,38 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +/** + * A NULL object. + */ +public abstract class ASN1Null + extends ASN1Object +{ + public ASN1Null() + { + } + + public int hashCode() + { + return -1; + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof ASN1Null)) + { + return false; + } + + return true; + } + + abstract void encode(DEROutputStream out) + throws IOException; + + public String toString() + { + return "NULL"; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1Object.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1Object.java new file mode 100644 index 000000000..5b57e0357 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1Object.java @@ -0,0 +1,38 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +public abstract class ASN1Object + extends DERObject +{ + /** + * Create a base ASN.1 object from a byte stream. + * + * @param data the byte stream to parse. + * @return the base ASN.1 object represented by the byte stream. + * @exception IOException if there is a problem parsing the data. + */ + public static ASN1Object fromByteArray(byte[] data) + throws IOException + { + ASN1InputStream aIn = new ASN1InputStream(data); + + return (ASN1Object)aIn.readObject(); + } + + public final boolean equals(Object o) + { + if (this == o) + { + return true; + } + + return (o instanceof DEREncodable) && asn1Equals(((DEREncodable)o).getDERObject()); + } + + public abstract int hashCode(); + + abstract void encode(DEROutputStream out) throws IOException; + + abstract boolean asn1Equals(DERObject o); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1ObjectParser.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1ObjectParser.java new file mode 100644 index 000000000..dc7a9fb15 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1ObjectParser.java @@ -0,0 +1,19 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.InputStream; + +/** + * @deprecated will be removed + */ +public class ASN1ObjectParser +{ + ASN1StreamParser _aIn; + + protected ASN1ObjectParser( + int baseTag, + int tagNumber, + InputStream contentStream) + { + _aIn = new ASN1StreamParser(contentStream); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1OctetString.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1OctetString.java new file mode 100644 index 000000000..a231cbf75 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1OctetString.java @@ -0,0 +1,135 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import com.google.bitcoin.bouncycastle.util.Arrays; +import com.google.bitcoin.bouncycastle.util.encoders.Hex; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Vector; + +public abstract class ASN1OctetString + extends ASN1Object + implements ASN1OctetStringParser +{ + byte[] string; + + /** + * return an Octet String from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static ASN1OctetString getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + /** + * return an Octet String from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static ASN1OctetString getInstance( + Object obj) + { + if (obj == null || obj instanceof ASN1OctetString) + { + return (ASN1OctetString)obj; + } + + if (obj instanceof ASN1TaggedObject) + { + return getInstance(((ASN1TaggedObject)obj).getObject()); + } + + if (obj instanceof ASN1Sequence) + { + Vector v = new Vector(); + Enumeration e = ((ASN1Sequence)obj).getObjects(); + + while (e.hasMoreElements()) + { + v.addElement(e.nextElement()); + } + + return new BERConstructedOctetString(v); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * @param string the octets making up the octet string. + */ + public ASN1OctetString( + byte[] string) + { + if (string == null) + { + throw new NullPointerException("string cannot be null"); + } + this.string = string; + } + + public ASN1OctetString( + DEREncodable obj) + { + try + { + this.string = obj.getDERObject().getEncoded(ASN1Encodable.DER); + } + catch (IOException e) + { + throw new IllegalArgumentException("Error processing object : " + e.toString()); + } + } + + public InputStream getOctetStream() + { + return new ByteArrayInputStream(string); + } + + public ASN1OctetStringParser parser() + { + return this; + } + + public byte[] getOctets() + { + return string; + } + + public int hashCode() + { + return Arrays.hashCode(this.getOctets()); + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof ASN1OctetString)) + { + return false; + } + + ASN1OctetString other = (ASN1OctetString)o; + + return Arrays.areEqual(string, other.string); + } + + abstract void encode(DEROutputStream out) + throws IOException; + + public String toString() + { + return "#"+new String(Hex.encode(string)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1OctetStringParser.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1OctetStringParser.java new file mode 100644 index 000000000..f5527cd60 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1OctetStringParser.java @@ -0,0 +1,9 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.InputStream; + +public interface ASN1OctetStringParser + extends DEREncodable +{ + public InputStream getOctetStream(); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1OutputStream.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1OutputStream.java new file mode 100644 index 000000000..f0b378661 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1OutputStream.java @@ -0,0 +1,36 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; +import java.io.OutputStream; + +public class ASN1OutputStream + extends DEROutputStream +{ + public ASN1OutputStream( + OutputStream os) + { + super(os); + } + + public void writeObject( + Object obj) + throws IOException + { + if (obj == null) + { + writeNull(); + } + else if (obj instanceof DERObject) + { + ((DERObject)obj).encode(this); + } + else if (obj instanceof DEREncodable) + { + ((DEREncodable)obj).getDERObject().encode(this); + } + else + { + throw new IOException("object not ASN1Encodable"); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1ParsingException.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1ParsingException.java new file mode 100644 index 000000000..17a56c55e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1ParsingException.java @@ -0,0 +1,23 @@ +package com.google.bitcoin.bouncycastle.asn1; + +public class ASN1ParsingException + extends IllegalStateException +{ + private Throwable cause; + + ASN1ParsingException(String message) + { + super(message); + } + + ASN1ParsingException(String message, Throwable cause) + { + super(message); + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1Sequence.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1Sequence.java new file mode 100644 index 000000000..d3fbfd748 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1Sequence.java @@ -0,0 +1,217 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.Vector; + +public abstract class ASN1Sequence + extends ASN1Object +{ + private Vector seq = new Vector(); + + /** + * return an ASN1Sequence from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static ASN1Sequence getInstance( + Object obj) + { + if (obj == null || obj instanceof ASN1Sequence) + { + return (ASN1Sequence)obj; + } + + throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName()); + } + + /** + * Return an ASN1 sequence from a tagged object. There is a special + * case here, if an object appears to have been explicitly tagged on + * reading but we were expecting it to be implicitly tagged in the + * normal course of events it indicates that we lost the surrounding + * sequence - so we need to add it back (this will happen if the tagged + * object is a sequence that contains other sequences). If you are + * dealing with implicitly tagged sequences you really should + * be using this method. + * + * @param obj the tagged object. + * @param explicit true if the object is meant to be explicitly tagged, + * false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static ASN1Sequence getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + if (explicit) + { + if (!obj.isExplicit()) + { + throw new IllegalArgumentException("object implicit - explicit expected."); + } + + return (ASN1Sequence)obj.getObject(); + } + else + { + // + // constructed object which appears to be explicitly tagged + // when it should be implicit means we have to add the + // surrounding sequence. + // + if (obj.isExplicit()) + { + if (obj instanceof BERTaggedObject) + { + return new BERSequence(obj.getObject()); + } + else + { + return new DERSequence(obj.getObject()); + } + } + else + { + if (obj.getObject() instanceof ASN1Sequence) + { + return (ASN1Sequence)obj.getObject(); + } + } + } + + throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName()); + } + + public Enumeration getObjects() + { + return seq.elements(); + } + + public ASN1SequenceParser parser() + { + final ASN1Sequence outer = this; + + return new ASN1SequenceParser() + { + private final int max = size(); + + private int index; + + public DEREncodable readObject() throws IOException + { + if (index == max) + { + return null; + } + + DEREncodable obj = getObjectAt(index++); + if (obj instanceof ASN1Sequence) + { + return ((ASN1Sequence)obj).parser(); + } + if (obj instanceof ASN1Set) + { + return ((ASN1Set)obj).parser(); + } + + return obj; + } + + public DERObject getDERObject() + { + return outer; + } + }; + } + + /** + * return the object at the sequence position indicated by index. + * + * @param index the sequence number (starting at zero) of the object + * @return the object at the sequence position indicated by index. + */ + public DEREncodable getObjectAt( + int index) + { + return (DEREncodable)seq.elementAt(index); + } + + /** + * return the number of objects in this sequence. + * + * @return the number of objects in this sequence. + */ + public int size() + { + return seq.size(); + } + + public int hashCode() + { + Enumeration e = this.getObjects(); + int hashCode = size(); + + while (e.hasMoreElements()) + { + Object o = e.nextElement(); + hashCode *= 17; + if (o != null) + { + hashCode ^= o.hashCode(); + } + } + + return hashCode; + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof ASN1Sequence)) + { + return false; + } + + ASN1Sequence other = (ASN1Sequence)o; + + if (this.size() != other.size()) + { + return false; + } + + Enumeration s1 = this.getObjects(); + Enumeration s2 = other.getObjects(); + + while (s1.hasMoreElements()) + { + DERObject o1 = ((DEREncodable)s1.nextElement()).getDERObject(); + DERObject o2 = ((DEREncodable)s2.nextElement()).getDERObject(); + + if (o1 == o2 || (o1 != null && o1.equals(o2))) + { + continue; + } + + return false; + } + + return true; + } + + protected void addObject( + DEREncodable obj) + { + seq.addElement(obj); + } + + abstract void encode(DEROutputStream out) + throws IOException; + + public String toString() + { + return seq.toString(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1SequenceParser.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1SequenceParser.java new file mode 100644 index 000000000..4928ec169 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1SequenceParser.java @@ -0,0 +1,10 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +public interface ASN1SequenceParser + extends DEREncodable +{ + DEREncodable readObject() + throws IOException; +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1Set.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1Set.java new file mode 100644 index 000000000..86f45c932 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1Set.java @@ -0,0 +1,343 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Vector; + +abstract public class ASN1Set + extends ASN1Object +{ + protected Vector set = new Vector(); + + /** + * return an ASN1Set from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static ASN1Set getInstance( + Object obj) + { + if (obj == null || obj instanceof ASN1Set) + { + return (ASN1Set)obj; + } + + throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName()); + } + + /** + * Return an ASN1 set from a tagged object. There is a special + * case here, if an object appears to have been explicitly tagged on + * reading but we were expecting it to be implicitly tagged in the + * normal course of events it indicates that we lost the surrounding + * set - so we need to add it back (this will happen if the tagged + * object is a sequence that contains other sequences). If you are + * dealing with implicitly tagged sets you really should + * be using this method. + * + * @param obj the tagged object. + * @param explicit true if the object is meant to be explicitly tagged + * false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static ASN1Set getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + if (explicit) + { + if (!obj.isExplicit()) + { + throw new IllegalArgumentException("object implicit - explicit expected."); + } + + return (ASN1Set)obj.getObject(); + } + else + { + // + // constructed object which appears to be explicitly tagged + // and it's really implicit means we have to add the + // surrounding sequence. + // + if (obj.isExplicit()) + { + ASN1Set set = new DERSet(obj.getObject()); + + return set; + } + else + { + if (obj.getObject() instanceof ASN1Set) + { + return (ASN1Set)obj.getObject(); + } + + // + // in this case the parser returns a sequence, convert it + // into a set. + // + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (obj.getObject() instanceof ASN1Sequence) + { + ASN1Sequence s = (ASN1Sequence)obj.getObject(); + Enumeration e = s.getObjects(); + + while (e.hasMoreElements()) + { + v.add((DEREncodable)e.nextElement()); + } + + return new DERSet(v, false); + } + } + } + + throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName()); + } + + public ASN1Set() + { + } + + public Enumeration getObjects() + { + return set.elements(); + } + + /** + * return the object at the set position indicated by index. + * + * @param index the set number (starting at zero) of the object + * @return the object at the set position indicated by index. + */ + public DEREncodable getObjectAt( + int index) + { + return (DEREncodable)set.elementAt(index); + } + + /** + * return the number of objects in this set. + * + * @return the number of objects in this set. + */ + public int size() + { + return set.size(); + } + + public ASN1SetParser parser() + { + final ASN1Set outer = this; + + return new ASN1SetParser() + { + private final int max = size(); + + private int index; + + public DEREncodable readObject() throws IOException + { + if (index == max) + { + return null; + } + + DEREncodable obj = getObjectAt(index++); + if (obj instanceof ASN1Sequence) + { + return ((ASN1Sequence)obj).parser(); + } + if (obj instanceof ASN1Set) + { + return ((ASN1Set)obj).parser(); + } + + return obj; + } + + public DERObject getDERObject() + { + return outer; + } + }; + } + + public int hashCode() + { + Enumeration e = this.getObjects(); + int hashCode = size(); + + while (e.hasMoreElements()) + { + Object o = e.nextElement(); + hashCode *= 17; + if (o != null) + { + hashCode ^= o.hashCode(); + } + } + + return hashCode; + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof ASN1Set)) + { + return false; + } + + ASN1Set other = (ASN1Set)o; + + if (this.size() != other.size()) + { + return false; + } + + Enumeration s1 = this.getObjects(); + Enumeration s2 = other.getObjects(); + + while (s1.hasMoreElements()) + { + DERObject o1 = ((DEREncodable)s1.nextElement()).getDERObject(); + DERObject o2 = ((DEREncodable)s2.nextElement()).getDERObject(); + + if (o1 == o2 || (o1 != null && o1.equals(o2))) + { + continue; + } + + return false; + } + + return true; + } + + /** + * return true if a <= b (arrays are assumed padded with zeros). + */ + private boolean lessThanOrEqual( + byte[] a, + byte[] b) + { + if (a.length <= b.length) + { + for (int i = 0; i != a.length; i++) + { + int l = a[i] & 0xff; + int r = b[i] & 0xff; + + if (r > l) + { + return true; + } + else if (l > r) + { + return false; + } + } + + return true; + } + else + { + for (int i = 0; i != b.length; i++) + { + int l = a[i] & 0xff; + int r = b[i] & 0xff; + + if (r > l) + { + return true; + } + else if (l > r) + { + return false; + } + } + + return false; + } + } + + private byte[] getEncoded( + DEREncodable obj) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ASN1OutputStream aOut = new ASN1OutputStream(bOut); + + try + { + aOut.writeObject(obj); + } + catch (IOException e) + { + throw new IllegalArgumentException("cannot encode object added to SET"); + } + + return bOut.toByteArray(); + } + + protected void sort() + { + if (set.size() > 1) + { + boolean swapped = true; + int lastSwap = set.size() - 1; + + while (swapped) + { + int index = 0; + int swapIndex = 0; + byte[] a = getEncoded((DEREncodable)set.elementAt(0)); + + swapped = false; + + while (index != lastSwap) + { + byte[] b = getEncoded((DEREncodable)set.elementAt(index + 1)); + + if (lessThanOrEqual(a, b)) + { + a = b; + } + else + { + Object o = set.elementAt(index); + + set.setElementAt(set.elementAt(index + 1), index); + set.setElementAt(o, index + 1); + + swapped = true; + swapIndex = index; + } + + index++; + } + + lastSwap = swapIndex; + } + } + } + + protected void addObject( + DEREncodable obj) + { + set.addElement(obj); + } + + abstract void encode(DEROutputStream out) + throws IOException; + + public String toString() + { + return set.toString(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1SetParser.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1SetParser.java new file mode 100644 index 000000000..be151687f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1SetParser.java @@ -0,0 +1,10 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +public interface ASN1SetParser + extends DEREncodable +{ + public DEREncodable readObject() + throws IOException; +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1StreamParser.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1StreamParser.java new file mode 100644 index 000000000..33fc15c0e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1StreamParser.java @@ -0,0 +1,174 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ASN1StreamParser +{ + private final InputStream _in; + private final int _limit; + + private static int findLimit(InputStream in) + { + if (in instanceof DefiniteLengthInputStream) + { + return ((DefiniteLengthInputStream)in).getRemaining(); + } + + return Integer.MAX_VALUE; + } + + public ASN1StreamParser( + InputStream in) + { + this(in, findLimit(in)); + } + + public ASN1StreamParser( + InputStream in, + int limit) + { + this._in = in; + this._limit = limit; + } + + public ASN1StreamParser( + byte[] encoding) + { + this(new ByteArrayInputStream(encoding), encoding.length); + } + + public DEREncodable readObject() + throws IOException + { + int tag = _in.read(); + if (tag == -1) + { + return null; + } + + // + // turn of looking for "00" while we resolve the tag + // + set00Check(false); + + // + // calculate tag number + // + int tagNo = ASN1InputStream.readTagNumber(_in, tag); + + boolean isConstructed = (tag & DERTags.CONSTRUCTED) != 0; + + // + // calculate length + // + int length = ASN1InputStream.readLength(_in, _limit); + + if (length < 0) // indefinite length method + { + if (!isConstructed) + { + throw new IOException("indefinite length primitive encoding encountered"); + } + + IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(_in); + + if ((tag & DERTags.APPLICATION) != 0) + { + ASN1StreamParser sp = new ASN1StreamParser(indIn, _limit); + + return new BERApplicationSpecificParser(tagNo, sp); + } + + if ((tag & DERTags.TAGGED) != 0) + { + return new BERTaggedObjectParser(tag, tagNo, indIn); + } + + ASN1StreamParser sp = new ASN1StreamParser(indIn, _limit); + + // TODO There are other tags that may be constructed (e.g. BIT_STRING) + switch (tagNo) + { + case DERTags.OCTET_STRING: + return new BEROctetStringParser(sp); + case DERTags.SEQUENCE: + return new BERSequenceParser(sp); + case DERTags.SET: + return new BERSetParser(sp); + case DERTags.EXTERNAL:{ + return new DERExternalParser(sp); + } + default: + throw new IOException("unknown BER object encountered: 0x" + Integer.toHexString(tagNo)); + } + } + else + { + DefiniteLengthInputStream defIn = new DefiniteLengthInputStream(_in, length); + + if ((tag & DERTags.APPLICATION) != 0) + { + return new DERApplicationSpecific(isConstructed, tagNo, defIn.toByteArray()); + } + + if ((tag & DERTags.TAGGED) != 0) + { + return new BERTaggedObjectParser(tag, tagNo, defIn); + } + + if (isConstructed) + { + // TODO There are other tags that may be constructed (e.g. BIT_STRING) + switch (tagNo) + { + case DERTags.OCTET_STRING: + // + // yes, people actually do this... + // + return new BEROctetStringParser(new ASN1StreamParser(defIn)); + case DERTags.SEQUENCE: + return new DERSequenceParser(new ASN1StreamParser(defIn)); + case DERTags.SET: + return new DERSetParser(new ASN1StreamParser(defIn)); + case DERTags.EXTERNAL: + return new DERExternalParser(new ASN1StreamParser(defIn)); + default: + // TODO Add DERUnknownTagParser class? + return new DERUnknownTag(true, tagNo, defIn.toByteArray()); + } + } + + // Some primitive encodings can be handled by parsers too... + switch (tagNo) + { + case DERTags.OCTET_STRING: + return new DEROctetStringParser(defIn); + } + + return ASN1InputStream.createPrimitiveDERObject(tagNo, defIn.toByteArray()); + } + } + + private void set00Check(boolean enabled) + { + if (_in instanceof IndefiniteLengthInputStream) + { + ((IndefiniteLengthInputStream)_in).setEofOn00(enabled); + } + } + + ASN1EncodableVector readVector() throws IOException + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + DEREncodable obj; + while ((obj = readObject()) != null) + { + v.add(obj.getDERObject()); + } + + return v; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1TaggedObject.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1TaggedObject.java new file mode 100644 index 000000000..918ff3e1c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1TaggedObject.java @@ -0,0 +1,210 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +/** + * ASN.1 TaggedObject - in ASN.1 notation this is any object preceded by + * a [n] where n is some number - these are assumed to follow the construction + * rules (as with sequences). + */ +public abstract class ASN1TaggedObject + extends ASN1Object + implements ASN1TaggedObjectParser +{ + int tagNo; + boolean empty = false; + boolean explicit = true; + DEREncodable obj = null; + + static public ASN1TaggedObject getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + if (explicit) + { + return (ASN1TaggedObject)obj.getObject(); + } + + throw new IllegalArgumentException("implicitly tagged tagged object"); + } + + static public ASN1TaggedObject getInstance( + Object obj) + { + if (obj == null || obj instanceof ASN1TaggedObject) + { + return (ASN1TaggedObject)obj; + } + + throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName()); + } + + /** + * Create a tagged object in the explicit style. + * + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + public ASN1TaggedObject( + int tagNo, + DEREncodable obj) + { + this.explicit = true; + this.tagNo = tagNo; + this.obj = obj; + } + + /** + * Create a tagged object with the style given by the value of explicit. + *

+ * If the object implements ASN1Choice the tag style will always be changed + * to explicit in accordance with the ASN.1 encoding rules. + *

+ * @param explicit true if the object is explicitly tagged. + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + public ASN1TaggedObject( + boolean explicit, + int tagNo, + DEREncodable obj) + { + if (obj instanceof ASN1Choice) + { + this.explicit = true; + } + else + { + this.explicit = explicit; + } + + this.tagNo = tagNo; + this.obj = obj; + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof ASN1TaggedObject)) + { + return false; + } + + ASN1TaggedObject other = (ASN1TaggedObject)o; + + if (tagNo != other.tagNo || empty != other.empty || explicit != other.explicit) + { + return false; + } + + if(obj == null) + { + if (other.obj != null) + { + return false; + } + } + else + { + if (!(obj.getDERObject().equals(other.obj.getDERObject()))) + { + return false; + } + } + + return true; + } + + public int hashCode() + { + int code = tagNo; + + // TODO: actually this is wrong - the problem is that a re-encoded + // object may end up with a different hashCode due to implicit + // tagging. As implicit tagging is ambiguous if a sequence is involved + // it seems the only correct method for both equals and hashCode is to + // compare the encodings... + if (obj != null) + { + code ^= obj.hashCode(); + } + + return code; + } + + public int getTagNo() + { + return tagNo; + } + + /** + * return whether or not the object may be explicitly tagged. + *

+ * Note: if the object has been read from an input stream, the only + * time you can be sure if isExplicit is returning the true state of + * affairs is if it returns false. An implicitly tagged object may appear + * to be explicitly tagged, so you need to understand the context under + * which the reading was done as well, see getObject below. + */ + public boolean isExplicit() + { + return explicit; + } + + public boolean isEmpty() + { + return empty; + } + + /** + * return whatever was following the tag. + *

+ * Note: tagged objects are generally context dependent if you're + * trying to extract a tagged object you should be going via the + * appropriate getInstance method. + */ + public DERObject getObject() + { + if (obj != null) + { + return obj.getDERObject(); + } + + return null; + } + + /** + * Return the object held in this tagged object as a parser assuming it has + * the type of the passed in tag. If the object doesn't have a parser + * associated with it, the base object is returned. + */ + public DEREncodable getObjectParser( + int tag, + boolean isExplicit) + { + switch (tag) + { + case DERTags.SET: + return ASN1Set.getInstance(this, isExplicit).parser(); + case DERTags.SEQUENCE: + return ASN1Sequence.getInstance(this, isExplicit).parser(); + case DERTags.OCTET_STRING: + return ASN1OctetString.getInstance(this, isExplicit).parser(); + } + + if (isExplicit) + { + return getObject(); + } + + throw new RuntimeException("implicit tagging not implemented for tag: " + tag); + } + + abstract void encode(DEROutputStream out) + throws IOException; + + public String toString() + { + return "[" + tagNo + "]" + obj; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ASN1TaggedObjectParser.java b/src/com/google/bitcoin/bouncycastle/asn1/ASN1TaggedObjectParser.java new file mode 100644 index 000000000..53562b62e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ASN1TaggedObjectParser.java @@ -0,0 +1,12 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +public interface ASN1TaggedObjectParser + extends DEREncodable +{ + public int getTagNo(); + + public DEREncodable getObjectParser(int tag, boolean isExplicit) + throws IOException; +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BERApplicationSpecific.java b/src/com/google/bitcoin/bouncycastle/asn1/BERApplicationSpecific.java new file mode 100644 index 000000000..685da4168 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BERApplicationSpecific.java @@ -0,0 +1,10 @@ +package com.google.bitcoin.bouncycastle.asn1; + +public class BERApplicationSpecific + extends DERApplicationSpecific +{ + public BERApplicationSpecific(int tagNo, ASN1EncodableVector vec) + { + super(tagNo, vec); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BERApplicationSpecificParser.java b/src/com/google/bitcoin/bouncycastle/asn1/BERApplicationSpecificParser.java new file mode 100644 index 000000000..212a4b90c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BERApplicationSpecificParser.java @@ -0,0 +1,34 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +public class BERApplicationSpecificParser + implements ASN1ApplicationSpecificParser +{ + private final int tag; + private final ASN1StreamParser parser; + + BERApplicationSpecificParser(int tag, ASN1StreamParser parser) + { + this.tag = tag; + this.parser = parser; + } + + public DEREncodable readObject() + throws IOException + { + return parser.readObject(); + } + + public DERObject getDERObject() + { + try + { + return new BERApplicationSpecific(tag, parser.readVector()); + } + catch (IOException e) + { + throw new ASN1ParsingException(e.getMessage(), e); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BERConstructedOctetString.java b/src/com/google/bitcoin/bouncycastle/asn1/BERConstructedOctetString.java new file mode 100644 index 000000000..951c7c29a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BERConstructedOctetString.java @@ -0,0 +1,144 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Vector; + +public class BERConstructedOctetString + extends DEROctetString +{ + private static final int MAX_LENGTH = 1000; + + /** + * convert a vector of octet strings into a single byte string + */ + static private byte[] toBytes( + Vector octs) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + for (int i = 0; i != octs.size(); i++) + { + try + { + DEROctetString o = (DEROctetString)octs.elementAt(i); + + bOut.write(o.getOctets()); + } + catch (ClassCastException e) + { + throw new IllegalArgumentException(octs.elementAt(i).getClass().getName() + " found in input should only contain DEROctetString"); + } + catch (IOException e) + { + throw new IllegalArgumentException("exception converting octets " + e.toString()); + } + } + + return bOut.toByteArray(); + } + + private Vector octs; + + /** + * @param string the octets making up the octet string. + */ + public BERConstructedOctetString( + byte[] string) + { + super(string); + } + + public BERConstructedOctetString( + Vector octs) + { + super(toBytes(octs)); + + this.octs = octs; + } + + public BERConstructedOctetString( + DERObject obj) + { + super(obj); + } + + public BERConstructedOctetString( + DEREncodable obj) + { + super(obj.getDERObject()); + } + + public byte[] getOctets() + { + return string; + } + + /** + * return the DER octets that make up this string. + */ + public Enumeration getObjects() + { + if (octs == null) + { + return generateOcts().elements(); + } + + return octs.elements(); + } + + private Vector generateOcts() + { + Vector vec = new Vector(); + for (int i = 0; i < string.length; i += MAX_LENGTH) + { + int end; + + if (i + MAX_LENGTH > string.length) + { + end = string.length; + } + else + { + end = i + MAX_LENGTH; + } + + byte[] nStr = new byte[end - i]; + + System.arraycopy(string, i, nStr, 0, nStr.length); + + vec.addElement(new DEROctetString(nStr)); + } + + return vec; + } + + public void encode( + DEROutputStream out) + throws IOException + { + if (out instanceof ASN1OutputStream || out instanceof BEROutputStream) + { + out.write(CONSTRUCTED | OCTET_STRING); + + out.write(0x80); + + // + // write out the octet array + // + Enumeration e = getObjects(); + while (e.hasMoreElements()) + { + out.writeObject(e.nextElement()); + } + + out.write(0x00); + out.write(0x00); + } + else + { + super.encode(out); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BERConstructedSequence.java b/src/com/google/bitcoin/bouncycastle/asn1/BERConstructedSequence.java new file mode 100644 index 000000000..768669235 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BERConstructedSequence.java @@ -0,0 +1,37 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; +import java.util.Enumeration; + +/** + * @deprecated use BERSequence + */ +public class BERConstructedSequence + extends DERConstructedSequence +{ + /* + */ + void encode( + DEROutputStream out) + throws IOException + { + if (out instanceof ASN1OutputStream || out instanceof BEROutputStream) + { + out.write(SEQUENCE | CONSTRUCTED); + out.write(0x80); + + Enumeration e = getObjects(); + while (e.hasMoreElements()) + { + out.writeObject(e.nextElement()); + } + + out.write(0x00); + out.write(0x00); + } + else + { + super.encode(out); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BERFactory.java b/src/com/google/bitcoin/bouncycastle/asn1/BERFactory.java new file mode 100644 index 000000000..2c4fce62d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BERFactory.java @@ -0,0 +1,22 @@ +package com.google.bitcoin.bouncycastle.asn1; + +class BERFactory +{ + static final BERSequence EMPTY_SEQUENCE = new BERSequence(); + static final BERSet EMPTY_SET = new BERSet(); + + static BERSequence createSequence(ASN1EncodableVector v) + { + return v.size() < 1 ? EMPTY_SEQUENCE : new BERSequence(v); + } + + static BERSet createSet(ASN1EncodableVector v) + { + return v.size() < 1 ? EMPTY_SET : new BERSet(v); + } + + static BERSet createSet(ASN1EncodableVector v, boolean needsSorting) + { + return v.size() < 1 ? EMPTY_SET : new BERSet(v, needsSorting); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BERGenerator.java b/src/com/google/bitcoin/bouncycastle/asn1/BERGenerator.java new file mode 100644 index 000000000..ebb17f7af --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BERGenerator.java @@ -0,0 +1,100 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class BERGenerator + extends ASN1Generator +{ + private boolean _tagged = false; + private boolean _isExplicit; + private int _tagNo; + + protected BERGenerator( + OutputStream out) + { + super(out); + } + + public BERGenerator( + OutputStream out, + int tagNo, + boolean isExplicit) + { + super(out); + + _tagged = true; + _isExplicit = isExplicit; + _tagNo = tagNo; + } + + public OutputStream getRawOutputStream() + { + return _out; + } + + private void writeHdr( + int tag) + throws IOException + { + _out.write(tag); + _out.write(0x80); + } + + protected void writeBERHeader( + int tag) + throws IOException + { + if (_tagged) + { + int tagNum = _tagNo | DERTags.TAGGED; + + if (_isExplicit) + { + writeHdr(tagNum | DERTags.CONSTRUCTED); + writeHdr(tag); + } + else + { + if ((tag & DERTags.CONSTRUCTED) != 0) + { + writeHdr(tagNum | DERTags.CONSTRUCTED); + } + else + { + writeHdr(tagNum); + } + } + } + else + { + writeHdr(tag); + } + } + + protected void writeBERBody( + InputStream contentStream) + throws IOException + { + int ch; + + while ((ch = contentStream.read()) >= 0) + { + _out.write(ch); + } + } + + protected void writeBEREnd() + throws IOException + { + _out.write(0x00); + _out.write(0x00); + + if (_tagged && _isExplicit) // write extra end for tag header + { + _out.write(0x00); + _out.write(0x00); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BERInputStream.java b/src/com/google/bitcoin/bouncycastle/asn1/BERInputStream.java new file mode 100644 index 000000000..22eee4199 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BERInputStream.java @@ -0,0 +1,209 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; + +/** + * @deprecated use ASN1InputStream + */ +public class BERInputStream + extends DERInputStream +{ + private static final DERObject END_OF_STREAM = new DERObject() + { + void encode( + DEROutputStream out) + throws IOException + { + throw new IOException("Eeek!"); + } + public int hashCode() + { + return 0; + } + public boolean equals( + Object o) + { + return o == this; + } + }; + public BERInputStream( + InputStream is) + { + super(is); + } + + /** + * read a string of bytes representing an indefinite length object. + */ + private byte[] readIndefiniteLengthFully() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + int b, b1; + + b1 = read(); + + while ((b = read()) >= 0) + { + if (b1 == 0 && b == 0) + { + break; + } + + bOut.write(b1); + b1 = b; + } + + return bOut.toByteArray(); + } + + private BERConstructedOctetString buildConstructedOctetString() + throws IOException + { + Vector octs = new Vector(); + + for (;;) + { + DERObject o = readObject(); + + if (o == END_OF_STREAM) + { + break; + } + + octs.addElement(o); + } + + return new BERConstructedOctetString(octs); + } + + public DERObject readObject() + throws IOException + { + int tag = read(); + if (tag == -1) + { + throw new EOFException(); + } + + int length = readLength(); + + if (length < 0) // indefinite length method + { + switch (tag) + { + case NULL: + return null; + case SEQUENCE | CONSTRUCTED: + BERConstructedSequence seq = new BERConstructedSequence(); + + for (;;) + { + DERObject obj = readObject(); + + if (obj == END_OF_STREAM) + { + break; + } + + seq.addObject(obj); + } + return seq; + case OCTET_STRING | CONSTRUCTED: + return buildConstructedOctetString(); + case SET | CONSTRUCTED: + ASN1EncodableVector v = new ASN1EncodableVector(); + + for (;;) + { + DERObject obj = readObject(); + + if (obj == END_OF_STREAM) + { + break; + } + + v.add(obj); + } + return new BERSet(v); + default: + // + // with tagged object tag number is bottom 5 bits + // + if ((tag & TAGGED) != 0) + { + if ((tag & 0x1f) == 0x1f) + { + throw new IOException("unsupported high tag encountered"); + } + + // + // simple type - implicit... return an octet string + // + if ((tag & CONSTRUCTED) == 0) + { + byte[] bytes = readIndefiniteLengthFully(); + + return new BERTaggedObject(false, tag & 0x1f, new DEROctetString(bytes)); + } + + // + // either constructed or explicitly tagged + // + DERObject dObj = readObject(); + + if (dObj == END_OF_STREAM) // empty tag! + { + return new DERTaggedObject(tag & 0x1f); + } + + DERObject next = readObject(); + + // + // explicitly tagged (probably!) - if it isn't we'd have to + // tell from the context + // + if (next == END_OF_STREAM) + { + return new BERTaggedObject(tag & 0x1f, dObj); + } + + // + // another implicit object, we'll create a sequence... + // + seq = new BERConstructedSequence(); + + seq.addObject(dObj); + + do + { + seq.addObject(next); + next = readObject(); + } + while (next != END_OF_STREAM); + + return new BERTaggedObject(false, tag & 0x1f, seq); + } + + throw new IOException("unknown BER object encountered"); + } + } + else + { + if (tag == 0 && length == 0) // end of contents marker. + { + return END_OF_STREAM; + } + + byte[] bytes = new byte[length]; + + readFully(bytes); + + return buildObject(tag, bytes); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BERNull.java b/src/com/google/bitcoin/bouncycastle/asn1/BERNull.java new file mode 100644 index 000000000..d12a24c64 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BERNull.java @@ -0,0 +1,30 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +/** + * A BER NULL object. + */ +public class BERNull + extends DERNull +{ + public static final BERNull INSTANCE = new BERNull(); + + public BERNull() + { + } + + void encode( + DEROutputStream out) + throws IOException + { + if (out instanceof ASN1OutputStream || out instanceof BEROutputStream) + { + out.write(NULL); + } + else + { + super.encode(out); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BEROctetStringGenerator.java b/src/com/google/bitcoin/bouncycastle/asn1/BEROctetStringGenerator.java new file mode 100644 index 000000000..a5a9cc28e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BEROctetStringGenerator.java @@ -0,0 +1,102 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; +import java.io.OutputStream; + +public class BEROctetStringGenerator + extends BERGenerator +{ + public BEROctetStringGenerator(OutputStream out) + throws IOException + { + super(out); + + writeBERHeader(DERTags.CONSTRUCTED | DERTags.OCTET_STRING); + } + + public BEROctetStringGenerator( + OutputStream out, + int tagNo, + boolean isExplicit) + throws IOException + { + super(out, tagNo, isExplicit); + + writeBERHeader(DERTags.CONSTRUCTED | DERTags.OCTET_STRING); + } + + public OutputStream getOctetOutputStream() + { + return getOctetOutputStream(new byte[1000]); // limit for CER encoding. + } + + public OutputStream getOctetOutputStream( + byte[] buf) + { + return new BufferedBEROctetStream(buf); + } + + private class BufferedBEROctetStream + extends OutputStream + { + private byte[] _buf; + private int _off; + private DEROutputStream _derOut; + + BufferedBEROctetStream( + byte[] buf) + { + _buf = buf; + _off = 0; + _derOut = new DEROutputStream(_out); + } + + public void write( + int b) + throws IOException + { + _buf[_off++] = (byte)b; + + if (_off == _buf.length) + { + DEROctetString.encode(_derOut, _buf); + _off = 0; + } + } + + public void write(byte[] b, int off, int len) throws IOException + { + while (len > 0) + { + int numToCopy = Math.min(len, _buf.length - _off); + System.arraycopy(b, off, _buf, _off, numToCopy); + + _off += numToCopy; + if (_off < _buf.length) + { + break; + } + + DEROctetString.encode(_derOut, _buf); + _off = 0; + + off += numToCopy; + len -= numToCopy; + } + } + + public void close() + throws IOException + { + if (_off != 0) + { + byte[] bytes = new byte[_off]; + System.arraycopy(_buf, 0, bytes, 0, _off); + + DEROctetString.encode(_derOut, bytes); + } + + writeBEREnd(); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BEROctetStringParser.java b/src/com/google/bitcoin/bouncycastle/asn1/BEROctetStringParser.java new file mode 100644 index 000000000..e3543d6de --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BEROctetStringParser.java @@ -0,0 +1,44 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import com.google.bitcoin.bouncycastle.util.io.Streams; + +import java.io.InputStream; +import java.io.IOException; + +public class BEROctetStringParser + implements ASN1OctetStringParser +{ + private ASN1StreamParser _parser; + + BEROctetStringParser( + ASN1StreamParser parser) + { + _parser = parser; + } + + /** + * @deprecated will be removed + */ + protected BEROctetStringParser( + ASN1ObjectParser parser) + { + _parser = parser._aIn; + } + + public InputStream getOctetStream() + { + return new ConstructedOctetStream(_parser); + } + + public DERObject getDERObject() + { + try + { + return new BERConstructedOctetString(Streams.readAll(getOctetStream())); + } + catch (IOException e) + { + throw new ASN1ParsingException("IOException converting stream to byte array: " + e.getMessage(), e); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BEROutputStream.java b/src/com/google/bitcoin/bouncycastle/asn1/BEROutputStream.java new file mode 100644 index 000000000..74d040197 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BEROutputStream.java @@ -0,0 +1,36 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; +import java.io.OutputStream; + +public class BEROutputStream + extends DEROutputStream +{ + public BEROutputStream( + OutputStream os) + { + super(os); + } + + public void writeObject( + Object obj) + throws IOException + { + if (obj == null) + { + writeNull(); + } + else if (obj instanceof DERObject) + { + ((DERObject)obj).encode(this); + } + else if (obj instanceof DEREncodable) + { + ((DEREncodable)obj).getDERObject().encode(this); + } + else + { + throw new IOException("object not BEREncodable"); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BERSequence.java b/src/com/google/bitcoin/bouncycastle/asn1/BERSequence.java new file mode 100644 index 000000000..8a0854cc6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BERSequence.java @@ -0,0 +1,59 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; +import java.util.Enumeration; + +public class BERSequence + extends DERSequence +{ + /** + * create an empty sequence + */ + public BERSequence() + { + } + + /** + * create a sequence containing one object + */ + public BERSequence( + DEREncodable obj) + { + super(obj); + } + + /** + * create a sequence containing a vector of objects. + */ + public BERSequence( + DEREncodableVector v) + { + super(v); + } + + /* + */ + void encode( + DEROutputStream out) + throws IOException + { + if (out instanceof ASN1OutputStream || out instanceof BEROutputStream) + { + out.write(SEQUENCE | CONSTRUCTED); + out.write(0x80); + + Enumeration e = getObjects(); + while (e.hasMoreElements()) + { + out.writeObject(e.nextElement()); + } + + out.write(0x00); + out.write(0x00); + } + else + { + super.encode(out); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BERSequenceGenerator.java b/src/com/google/bitcoin/bouncycastle/asn1/BERSequenceGenerator.java new file mode 100644 index 000000000..a527dcaa4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BERSequenceGenerator.java @@ -0,0 +1,41 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; +import java.io.OutputStream; + +public class BERSequenceGenerator + extends BERGenerator +{ + public BERSequenceGenerator( + OutputStream out) + throws IOException + { + super(out); + + writeBERHeader(DERTags.CONSTRUCTED | DERTags.SEQUENCE); + } + + public BERSequenceGenerator( + OutputStream out, + int tagNo, + boolean isExplicit) + throws IOException + { + super(out, tagNo, isExplicit); + + writeBERHeader(DERTags.CONSTRUCTED | DERTags.SEQUENCE); + } + + public void addObject( + DEREncodable object) + throws IOException + { + object.getDERObject().encode(new BEROutputStream(_out)); + } + + public void close() + throws IOException + { + writeBEREnd(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BERSequenceParser.java b/src/com/google/bitcoin/bouncycastle/asn1/BERSequenceParser.java new file mode 100644 index 000000000..14fe0147d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BERSequenceParser.java @@ -0,0 +1,32 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +public class BERSequenceParser + implements ASN1SequenceParser +{ + private ASN1StreamParser _parser; + + BERSequenceParser(ASN1StreamParser parser) + { + this._parser = parser; + } + + public DEREncodable readObject() + throws IOException + { + return _parser.readObject(); + } + + public DERObject getDERObject() + { + try + { + return new BERSequence(_parser.readVector()); + } + catch (IOException e) + { + throw new IllegalStateException(e.getMessage()); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BERSet.java b/src/com/google/bitcoin/bouncycastle/asn1/BERSet.java new file mode 100644 index 000000000..b63520c13 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BERSet.java @@ -0,0 +1,69 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; +import java.util.Enumeration; + +public class BERSet + extends DERSet +{ + /** + * create an empty sequence + */ + public BERSet() + { + } + + /** + * create a set containing one object + */ + public BERSet( + DEREncodable obj) + { + super(obj); + } + + /** + * @param v - a vector of objects making up the set. + */ + public BERSet( + DEREncodableVector v) + { + super(v, false); + } + + /** + * @param v - a vector of objects making up the set. + */ + BERSet( + DEREncodableVector v, + boolean needsSorting) + { + super(v, needsSorting); + } + + /* + */ + void encode( + DEROutputStream out) + throws IOException + { + if (out instanceof ASN1OutputStream || out instanceof BEROutputStream) + { + out.write(SET | CONSTRUCTED); + out.write(0x80); + + Enumeration e = getObjects(); + while (e.hasMoreElements()) + { + out.writeObject(e.nextElement()); + } + + out.write(0x00); + out.write(0x00); + } + else + { + super.encode(out); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BERSetParser.java b/src/com/google/bitcoin/bouncycastle/asn1/BERSetParser.java new file mode 100644 index 000000000..3d17ca625 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BERSetParser.java @@ -0,0 +1,32 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +public class BERSetParser + implements ASN1SetParser +{ + private ASN1StreamParser _parser; + + BERSetParser(ASN1StreamParser parser) + { + this._parser = parser; + } + + public DEREncodable readObject() + throws IOException + { + return _parser.readObject(); + } + + public DERObject getDERObject() + { + try + { + return new BERSet(_parser.readVector(), false); + } + catch (IOException e) + { + throw new ASN1ParsingException(e.getMessage(), e); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BERTaggedObject.java b/src/com/google/bitcoin/bouncycastle/asn1/BERTaggedObject.java new file mode 100644 index 000000000..cc93ba863 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BERTaggedObject.java @@ -0,0 +1,107 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; +import java.util.Enumeration; + +/** + * BER TaggedObject - in ASN.1 notation this is any object preceded by + * a [n] where n is some number - these are assumed to follow the construction + * rules (as with sequences). + */ +public class BERTaggedObject + extends DERTaggedObject +{ + /** + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + public BERTaggedObject( + int tagNo, + DEREncodable obj) + { + super(tagNo, obj); + } + + /** + * @param explicit true if an explicitly tagged object. + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + public BERTaggedObject( + boolean explicit, + int tagNo, + DEREncodable obj) + { + super(explicit, tagNo, obj); + } + + /** + * create an implicitly tagged object that contains a zero + * length sequence. + */ + public BERTaggedObject( + int tagNo) + { + super(false, tagNo, new BERSequence()); + } + + void encode( + DEROutputStream out) + throws IOException + { + if (out instanceof ASN1OutputStream || out instanceof BEROutputStream) + { + out.writeTag(CONSTRUCTED | TAGGED, tagNo); + out.write(0x80); + + if (!empty) + { + if (!explicit) + { + Enumeration e; + if (obj instanceof ASN1OctetString) + { + if (obj instanceof BERConstructedOctetString) + { + e = ((BERConstructedOctetString)obj).getObjects(); + } + else + { + ASN1OctetString octs = (ASN1OctetString)obj; + BERConstructedOctetString berO = new BERConstructedOctetString(octs.getOctets()); + e = berO.getObjects(); + } + } + else if (obj instanceof ASN1Sequence) + { + e = ((ASN1Sequence)obj).getObjects(); + } + else if (obj instanceof ASN1Set) + { + e = ((ASN1Set)obj).getObjects(); + } + else + { + throw new RuntimeException("not implemented: " + obj.getClass().getName()); + } + + while (e.hasMoreElements()) + { + out.writeObject(e.nextElement()); + } + } + else + { + out.writeObject(obj); + } + } + + out.write(0x00); + out.write(0x00); + } + else + { + super.encode(out); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/BERTaggedObjectParser.java b/src/com/google/bitcoin/bouncycastle/asn1/BERTaggedObjectParser.java new file mode 100644 index 000000000..01041ef1e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/BERTaggedObjectParser.java @@ -0,0 +1,123 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; +import java.io.InputStream; + +public class BERTaggedObjectParser + implements ASN1TaggedObjectParser +{ + private int _baseTag; + private int _tagNumber; + private InputStream _contentStream; + + private boolean _indefiniteLength; + + protected BERTaggedObjectParser( + int baseTag, + int tagNumber, + InputStream contentStream) + { + _baseTag = baseTag; + _tagNumber = tagNumber; + _contentStream = contentStream; + _indefiniteLength = contentStream instanceof IndefiniteLengthInputStream; + } + + public boolean isConstructed() + { + return (_baseTag & DERTags.CONSTRUCTED) != 0; + } + + public int getTagNo() + { + return _tagNumber; + } + + public DEREncodable getObjectParser( + int tag, + boolean isExplicit) + throws IOException + { + if (isExplicit) + { + return new ASN1StreamParser(_contentStream).readObject(); + } + + switch (tag) + { + case DERTags.SET: + if (_indefiniteLength) + { + return new BERSetParser(new ASN1StreamParser(_contentStream)); + } + else + { + return new DERSetParser(new ASN1StreamParser(_contentStream)); + } + case DERTags.SEQUENCE: + if (_indefiniteLength) + { + return new BERSequenceParser(new ASN1StreamParser(_contentStream)); + } + else + { + return new DERSequenceParser(new ASN1StreamParser(_contentStream)); + } + case DERTags.OCTET_STRING: + // TODO Is the handling of definite length constructed encodings correct? + if (_indefiniteLength || this.isConstructed()) + { + return new BEROctetStringParser(new ASN1StreamParser(_contentStream)); + } + else + { + return new DEROctetStringParser((DefiniteLengthInputStream)_contentStream); + } + } + + throw new RuntimeException("implicit tagging not implemented"); + } + + private ASN1EncodableVector rLoadVector(InputStream in) + { + try + { + return new ASN1StreamParser(in).readVector(); + } + catch (IOException e) + { + throw new ASN1ParsingException(e.getMessage(), e); + } + } + + public DERObject getDERObject() + { + if (_indefiniteLength) + { + ASN1EncodableVector v = rLoadVector(_contentStream); + + return v.size() == 1 + ? new BERTaggedObject(true, _tagNumber, v.get(0)) + : new BERTaggedObject(false, _tagNumber, BERFactory.createSequence(v)); + } + + if (this.isConstructed()) + { + ASN1EncodableVector v = rLoadVector(_contentStream); + + return v.size() == 1 + ? new DERTaggedObject(true, _tagNumber, v.get(0)) + : new DERTaggedObject(false, _tagNumber, DERFactory.createSequence(v)); + } + + try + { + DefiniteLengthInputStream defIn = (DefiniteLengthInputStream)_contentStream; + return new DERTaggedObject(false, _tagNumber, new DEROctetString(defIn.toByteArray())); + } + catch (IOException e) + { + throw new IllegalStateException(e.getMessage()); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ConstructedOctetStream.java b/src/com/google/bitcoin/bouncycastle/asn1/ConstructedOctetStream.java new file mode 100644 index 000000000..e147c9dfd --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ConstructedOctetStream.java @@ -0,0 +1,111 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.InputStream; +import java.io.IOException; + +class ConstructedOctetStream + extends InputStream +{ + private final ASN1StreamParser _parser; + + private boolean _first = true; + private InputStream _currentStream; + + ConstructedOctetStream( + ASN1StreamParser parser) + { + _parser = parser; + } + + public int read(byte[] b, int off, int len) throws IOException + { + if (_currentStream == null) + { + if (!_first) + { + return -1; + } + + ASN1OctetStringParser s = (ASN1OctetStringParser)_parser.readObject(); + + if (s == null) + { + return -1; + } + + _first = false; + _currentStream = s.getOctetStream(); + } + + int totalRead = 0; + + for (;;) + { + int numRead = _currentStream.read(b, off + totalRead, len - totalRead); + + if (numRead >= 0) + { + totalRead += numRead; + + if (totalRead == len) + { + return totalRead; + } + } + else + { + ASN1OctetStringParser aos = (ASN1OctetStringParser)_parser.readObject(); + + if (aos == null) + { + _currentStream = null; + return totalRead < 1 ? -1 : totalRead; + } + + _currentStream = aos.getOctetStream(); + } + } + } + + public int read() + throws IOException + { + if (_currentStream == null) + { + if (!_first) + { + return -1; + } + + ASN1OctetStringParser s = (ASN1OctetStringParser)_parser.readObject(); + + if (s == null) + { + return -1; + } + + _first = false; + _currentStream = s.getOctetStream(); + } + + for (;;) + { + int b = _currentStream.read(); + + if (b >= 0) + { + return b; + } + + ASN1OctetStringParser s = (ASN1OctetStringParser)_parser.readObject(); + + if (s == null) + { + _currentStream = null; + return -1; + } + + _currentStream = s.getOctetStream(); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERApplicationSpecific.java b/src/com/google/bitcoin/bouncycastle/asn1/DERApplicationSpecific.java new file mode 100644 index 000000000..dda511967 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERApplicationSpecific.java @@ -0,0 +1,225 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import com.google.bitcoin.bouncycastle.util.Arrays; + +/** + * Base class for an application specific object + */ +public class DERApplicationSpecific + extends ASN1Object +{ + private final boolean isConstructed; + private final int tag; + private final byte[] octets; + + DERApplicationSpecific( + boolean isConstructed, + int tag, + byte[] octets) + { + this.isConstructed = isConstructed; + this.tag = tag; + this.octets = octets; + } + + public DERApplicationSpecific( + int tag, + byte[] octets) + { + this(false, tag, octets); + } + + public DERApplicationSpecific( + int tag, + DEREncodable object) + throws IOException + { + this(true, tag, object); + } + + public DERApplicationSpecific( + boolean explicit, + int tag, + DEREncodable object) + throws IOException + { + byte[] data = object.getDERObject().getDEREncoded(); + + this.isConstructed = explicit; + this.tag = tag; + + if (explicit) + { + this.octets = data; + } + else + { + int lenBytes = getLengthOfLength(data); + byte[] tmp = new byte[data.length - lenBytes]; + System.arraycopy(data, lenBytes, tmp, 0, tmp.length); + this.octets = tmp; + } + } + + public DERApplicationSpecific(int tagNo, ASN1EncodableVector vec) + { + this.tag = tagNo; + this.isConstructed = true; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + for (int i = 0; i != vec.size(); i++) + { + try + { + bOut.write(((ASN1Encodable)vec.get(i)).getEncoded()); + } + catch (IOException e) + { + throw new ASN1ParsingException("malformed object: " + e, e); + } + } + this.octets = bOut.toByteArray(); + } + + private int getLengthOfLength(byte[] data) + { + int count = 2; // TODO: assumes only a 1 byte tag number + + while((data[count - 1] & 0x80) != 0) + { + count++; + } + + return count; + } + + public boolean isConstructed() + { + return isConstructed; + } + + public byte[] getContents() + { + return octets; + } + + public int getApplicationTag() + { + return tag; + } + + /** + * Return the enclosed object assuming explicit tagging. + * + * @return the resulting object + * @throws IOException if reconstruction fails. + */ + public DERObject getObject() + throws IOException + { + return new ASN1InputStream(getContents()).readObject(); + } + + /** + * Return the enclosed object assuming implicit tagging. + * + * @param derTagNo the type tag that should be applied to the object's contents. + * @return the resulting object + * @throws IOException if reconstruction fails. + */ + public DERObject getObject(int derTagNo) + throws IOException + { + if (derTagNo >= 0x1f) + { + throw new IOException("unsupported tag number"); + } + + byte[] orig = this.getEncoded(); + byte[] tmp = replaceTagNumber(derTagNo, orig); + + if ((orig[0] & DERTags.CONSTRUCTED) != 0) + { + tmp[0] |= DERTags.CONSTRUCTED; + } + + return new ASN1InputStream(tmp).readObject(); + } + + /* (non-Javadoc) + * @see org.bouncycastle.asn1.DERObject#encode(org.bouncycastle.asn1.DEROutputStream) + */ + void encode(DEROutputStream out) throws IOException + { + int classBits = DERTags.APPLICATION; + if (isConstructed) + { + classBits |= DERTags.CONSTRUCTED; + } + + out.writeEncoded(classBits, tag, octets); + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof DERApplicationSpecific)) + { + return false; + } + + DERApplicationSpecific other = (DERApplicationSpecific)o; + + return isConstructed == other.isConstructed + && tag == other.tag + && Arrays.areEqual(octets, other.octets); + } + + public int hashCode() + { + return (isConstructed ? 1 : 0) ^ tag ^ Arrays.hashCode(octets); + } + + private byte[] replaceTagNumber(int newTag, byte[] input) + throws IOException + { + int tagNo = input[0] & 0x1f; + int index = 1; + // + // with tagged object tag number is bottom 5 bits, or stored at the start of the content + // + if (tagNo == 0x1f) + { + tagNo = 0; + + int b = input[index++] & 0xff; + + // X.690-0207 8.1.2.4.2 + // "c) bits 7 to 1 of the first subsequent octet shall not all be zero." + if ((b & 0x7f) == 0) // Note: -1 will pass + { + throw new ASN1ParsingException("corrupted stream - invalid high tag number found"); + } + + while ((b >= 0) && ((b & 0x80) != 0)) + { + tagNo |= (b & 0x7f); + tagNo <<= 7; + b = input[index++] & 0xff; + } + + tagNo |= (b & 0x7f); + } + + byte[] tmp = new byte[input.length - index + 1]; + + System.arraycopy(input, index, tmp, 1, tmp.length - 1); + + tmp[0] = (byte)newTag; + + return tmp; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERBMPString.java b/src/com/google/bitcoin/bouncycastle/asn1/DERBMPString.java new file mode 100644 index 000000000..4077266ea --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERBMPString.java @@ -0,0 +1,126 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +/** + * DER BMPString object. + */ +public class DERBMPString + extends ASN1Object + implements DERString +{ + String string; + + /** + * return a BMP String from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static DERBMPString getInstance( + Object obj) + { + if (obj == null || obj instanceof DERBMPString) + { + return (DERBMPString)obj; + } + + if (obj instanceof ASN1OctetString) + { + return new DERBMPString(((ASN1OctetString)obj).getOctets()); + } + + if (obj instanceof ASN1TaggedObject) + { + return getInstance(((ASN1TaggedObject)obj).getObject()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * return a BMP String from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static DERBMPString getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + + /** + * basic constructor - byte encoded string. + */ + public DERBMPString( + byte[] string) + { + char[] cs = new char[string.length / 2]; + + for (int i = 0; i != cs.length; i++) + { + cs[i] = (char)((string[2 * i] << 8) | (string[2 * i + 1] & 0xff)); + } + + this.string = new String(cs); + } + + /** + * basic constructor + */ + public DERBMPString( + String string) + { + this.string = string; + } + + public String getString() + { + return string; + } + + public String toString() + { + return string; + } + + public int hashCode() + { + return this.getString().hashCode(); + } + + protected boolean asn1Equals( + DERObject o) + { + if (!(o instanceof DERBMPString)) + { + return false; + } + + DERBMPString s = (DERBMPString)o; + + return this.getString().equals(s.getString()); + } + + void encode( + DEROutputStream out) + throws IOException + { + char[] c = string.toCharArray(); + byte[] b = new byte[c.length * 2]; + + for (int i = 0; i != c.length; i++) + { + b[2 * i] = (byte)(c[i] >> 8); + b[2 * i + 1] = (byte)c[i]; + } + + out.writeEncoded(BMP_STRING, b); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERBitString.java b/src/com/google/bitcoin/bouncycastle/asn1/DERBitString.java new file mode 100644 index 000000000..b6d971f76 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERBitString.java @@ -0,0 +1,266 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import com.google.bitcoin.bouncycastle.util.Arrays; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class DERBitString + extends ASN1Object + implements DERString +{ + private static final char[] table = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + protected byte[] data; + protected int padBits; + + /** + * return the correct number of pad bits for a bit string defined in + * a 32 bit constant + */ + static protected int getPadBits( + int bitString) + { + int val = 0; + for (int i = 3; i >= 0; i--) + { + // + // this may look a little odd, but if it isn't done like this pre jdk1.2 + // JVM's break! + // + if (i != 0) + { + if ((bitString >> (i * 8)) != 0) + { + val = (bitString >> (i * 8)) & 0xFF; + break; + } + } + else + { + if (bitString != 0) + { + val = bitString & 0xFF; + break; + } + } + } + + if (val == 0) + { + return 7; + } + + + int bits = 1; + + while (((val <<= 1) & 0xFF) != 0) + { + bits++; + } + + return 8 - bits; + } + + /** + * return the correct number of bytes for a bit string defined in + * a 32 bit constant + */ + static protected byte[] getBytes(int bitString) + { + int bytes = 4; + for (int i = 3; i >= 1; i--) + { + if ((bitString & (0xFF << (i * 8))) != 0) + { + break; + } + bytes--; + } + + byte[] result = new byte[bytes]; + for (int i = 0; i < bytes; i++) + { + result[i] = (byte) ((bitString >> (i * 8)) & 0xFF); + } + + return result; + } + + /** + * return a Bit String from the passed in object + * + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static DERBitString getInstance( + Object obj) + { + if (obj == null || obj instanceof DERBitString) + { + return (DERBitString)obj; + } + + if (obj instanceof ASN1OctetString) + { + byte[] bytes = ((ASN1OctetString)obj).getOctets(); + int padBits = bytes[0]; + byte[] data = new byte[bytes.length - 1]; + + System.arraycopy(bytes, 1, data, 0, bytes.length - 1); + + return new DERBitString(data, padBits); + } + + if (obj instanceof ASN1TaggedObject) + { + return getInstance(((ASN1TaggedObject)obj).getObject()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * return a Bit String from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static DERBitString getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + protected DERBitString( + byte data, + int padBits) + { + this.data = new byte[1]; + this.data[0] = data; + this.padBits = padBits; + } + + /** + * @param data the octets making up the bit string. + * @param padBits the number of extra bits at the end of the string. + */ + public DERBitString( + byte[] data, + int padBits) + { + this.data = data; + this.padBits = padBits; + } + + public DERBitString( + byte[] data) + { + this(data, 0); + } + + public DERBitString( + DEREncodable obj) + { + try + { + this.data = obj.getDERObject().getEncoded(ASN1Encodable.DER); + this.padBits = 0; + } + catch (IOException e) + { + throw new IllegalArgumentException("Error processing object : " + e.toString()); + } + } + + public byte[] getBytes() + { + return data; + } + + public int getPadBits() + { + return padBits; + } + + + /** + * @return the value of the bit string as an int (truncating if necessary) + */ + public int intValue() + { + int value = 0; + + for (int i = 0; i != data.length && i != 4; i++) + { + value |= (data[i] & 0xff) << (8 * i); + } + + return value; + } + + void encode( + DEROutputStream out) + throws IOException + { + byte[] bytes = new byte[getBytes().length + 1]; + + bytes[0] = (byte)getPadBits(); + System.arraycopy(getBytes(), 0, bytes, 1, bytes.length - 1); + + out.writeEncoded(BIT_STRING, bytes); + } + + public int hashCode() + { + return padBits ^ Arrays.hashCode(data); + } + + protected boolean asn1Equals( + DERObject o) + { + if (!(o instanceof DERBitString)) + { + return false; + } + + DERBitString other = (DERBitString)o; + + return this.padBits == other.padBits + && Arrays.areEqual(this.data, other.data); + } + + public String getString() + { + StringBuffer buf = new StringBuffer("#"); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ASN1OutputStream aOut = new ASN1OutputStream(bOut); + + try + { + aOut.writeObject(this); + } + catch (IOException e) + { + throw new RuntimeException("internal error encoding BitString"); + } + + byte[] string = bOut.toByteArray(); + + for (int i = 0; i != string.length; i++) + { + buf.append(table[(string[i] >>> 4) & 0xf]); + buf.append(table[string[i] & 0xf]); + } + + return buf.toString(); + } + + public String toString() + { + return getString(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERBoolean.java b/src/com/google/bitcoin/bouncycastle/asn1/DERBoolean.java new file mode 100644 index 000000000..adfdcdaed --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERBoolean.java @@ -0,0 +1,113 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +public class DERBoolean + extends ASN1Object +{ + byte value; + + public static final DERBoolean FALSE = new DERBoolean(false); + public static final DERBoolean TRUE = new DERBoolean(true); + + /** + * return a boolean from the passed in object. + * + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static DERBoolean getInstance( + Object obj) + { + if (obj == null || obj instanceof DERBoolean) + { + return (DERBoolean)obj; + } + + if (obj instanceof ASN1OctetString) + { + return new DERBoolean(((ASN1OctetString)obj).getOctets()); + } + + if (obj instanceof ASN1TaggedObject) + { + return getInstance(((ASN1TaggedObject)obj).getObject()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * return a DERBoolean from the passed in boolean. + */ + public static DERBoolean getInstance( + boolean value) + { + return (value ? TRUE : FALSE); + } + + /** + * return a Boolean from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static DERBoolean getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + public DERBoolean( + byte[] value) + { + this.value = value[0]; + } + + public DERBoolean( + boolean value) + { + this.value = (value) ? (byte)0xff : (byte)0; + } + + public boolean isTrue() + { + return (value != 0); + } + + void encode( + DEROutputStream out) + throws IOException + { + byte[] bytes = new byte[1]; + + bytes[0] = value; + + out.writeEncoded(BOOLEAN, bytes); + } + + protected boolean asn1Equals( + DERObject o) + { + if ((o == null) || !(o instanceof DERBoolean)) + { + return false; + } + + return (value == ((DERBoolean)o).value); + } + + public int hashCode() + { + return value; + } + + + public String toString() + { + return (value != 0) ? "TRUE" : "FALSE"; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERConstructedSequence.java b/src/com/google/bitcoin/bouncycastle/asn1/DERConstructedSequence.java new file mode 100644 index 000000000..584202680 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERConstructedSequence.java @@ -0,0 +1,53 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Enumeration; + +/** + * @deprecated use DERSequence. + */ +public class DERConstructedSequence + extends ASN1Sequence +{ + public void addObject( + DEREncodable obj) + { + super.addObject(obj); + } + + public int getSize() + { + return size(); + } + + /* + * A note on the implementation: + *

+ * As DER requires the constructed, definite-length model to + * be used for structured types, this varies slightly from the + * ASN.1 descriptions given. Rather than just outputing SEQUENCE, + * we also have to specify CONSTRUCTED, and the objects length. + */ + void encode( + DEROutputStream out) + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + DEROutputStream dOut = new DEROutputStream(bOut); + Enumeration e = this.getObjects(); + + while (e.hasMoreElements()) + { + Object obj = e.nextElement(); + + dOut.writeObject(obj); + } + + dOut.close(); + + byte[] bytes = bOut.toByteArray(); + + out.writeEncoded(SEQUENCE | CONSTRUCTED, bytes); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERConstructedSet.java b/src/com/google/bitcoin/bouncycastle/asn1/DERConstructedSet.java new file mode 100644 index 000000000..717a160bd --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERConstructedSet.java @@ -0,0 +1,79 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Enumeration; + +/** + * + * @deprecated use DERSet + */ +public class DERConstructedSet + extends ASN1Set +{ + public DERConstructedSet() + { + } + + /** + * @param obj - a single object that makes up the set. + */ + public DERConstructedSet( + DEREncodable obj) + { + this.addObject(obj); + } + + /** + * @param v - a vector of objects making up the set. + */ + public DERConstructedSet( + DEREncodableVector v) + { + for (int i = 0; i != v.size(); i++) + { + this.addObject(v.get(i)); + } + } + + public void addObject( + DEREncodable obj) + { + super.addObject(obj); + } + + public int getSize() + { + return size(); + } + + /* + * A note on the implementation: + *

+ * As DER requires the constructed, definite-length model to + * be used for structured types, this varies slightly from the + * ASN.1 descriptions given. Rather than just outputing SET, + * we also have to specify CONSTRUCTED, and the objects length. + */ + void encode( + DEROutputStream out) + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + DEROutputStream dOut = new DEROutputStream(bOut); + Enumeration e = this.getObjects(); + + while (e.hasMoreElements()) + { + Object obj = e.nextElement(); + + dOut.writeObject(obj); + } + + dOut.close(); + + byte[] bytes = bOut.toByteArray(); + + out.writeEncoded(SET | CONSTRUCTED, bytes); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DEREncodable.java b/src/com/google/bitcoin/bouncycastle/asn1/DEREncodable.java new file mode 100644 index 000000000..919472676 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DEREncodable.java @@ -0,0 +1,6 @@ +package com.google.bitcoin.bouncycastle.asn1; + +public interface DEREncodable +{ + public DERObject getDERObject(); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DEREncodableVector.java b/src/com/google/bitcoin/bouncycastle/asn1/DEREncodableVector.java new file mode 100644 index 000000000..511f9b566 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DEREncodableVector.java @@ -0,0 +1,38 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.util.Vector; + +/** + * a general class for building up a vector of DER encodable objects - + * this will eventually be superceded by ASN1EncodableVector so you should + * use that class in preference. + */ +public class DEREncodableVector +{ + Vector v = new Vector(); + + /** + * @deprecated use ASN1EncodableVector instead. + */ + public DEREncodableVector() + { + + } + + public void add( + DEREncodable obj) + { + v.addElement(obj); + } + + public DEREncodable get( + int i) + { + return (DEREncodable)v.elementAt(i); + } + + public int size() + { + return v.size(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DEREnumerated.java b/src/com/google/bitcoin/bouncycastle/asn1/DEREnumerated.java new file mode 100644 index 000000000..a991a30c7 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DEREnumerated.java @@ -0,0 +1,102 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import com.google.bitcoin.bouncycastle.util.Arrays; + +import java.io.IOException; +import java.math.BigInteger; + +public class DEREnumerated + extends ASN1Object +{ + byte[] bytes; + + /** + * return an integer from the passed in object + * + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static DEREnumerated getInstance( + Object obj) + { + if (obj == null || obj instanceof DEREnumerated) + { + return (DEREnumerated)obj; + } + + if (obj instanceof ASN1OctetString) + { + return new DEREnumerated(((ASN1OctetString)obj).getOctets()); + } + + if (obj instanceof ASN1TaggedObject) + { + return getInstance(((ASN1TaggedObject)obj).getObject()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * return an Enumerated from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static DEREnumerated getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + public DEREnumerated( + int value) + { + bytes = BigInteger.valueOf(value).toByteArray(); + } + + public DEREnumerated( + BigInteger value) + { + bytes = value.toByteArray(); + } + + public DEREnumerated( + byte[] bytes) + { + this.bytes = bytes; + } + + public BigInteger getValue() + { + return new BigInteger(bytes); + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(ENUMERATED, bytes); + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof DEREnumerated)) + { + return false; + } + + DEREnumerated other = (DEREnumerated)o; + + return Arrays.areEqual(this.bytes, other.bytes); + } + + public int hashCode() + { + return Arrays.hashCode(bytes); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERExternal.java b/src/com/google/bitcoin/bouncycastle/asn1/DERExternal.java new file mode 100644 index 000000000..627c1ba62 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERExternal.java @@ -0,0 +1,267 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Class representing the DER-type External + */ +public class DERExternal + extends ASN1Object +{ + private DERObjectIdentifier directReference; + private DERInteger indirectReference; + private ASN1Object dataValueDescriptor; + private int encoding; + private DERObject externalContent; + + public DERExternal(ASN1EncodableVector vector) + { + int offset = 0; + DERObject enc = vector.get(offset).getDERObject(); + if (enc instanceof DERObjectIdentifier) + { + directReference = (DERObjectIdentifier)enc; + offset++; + enc = vector.get(offset).getDERObject(); + } + if (enc instanceof DERInteger) + { + indirectReference = (DERInteger) enc; + offset++; + enc = vector.get(offset).getDERObject(); + } + if (!(enc instanceof DERTaggedObject)) + { + dataValueDescriptor = (ASN1Object) enc; + offset++; + enc = vector.get(offset).getDERObject(); + } + if (!(enc instanceof DERTaggedObject)) + { + throw new IllegalArgumentException("No tagged object found in vector. Structure doesn't seem to be of type External"); + } + DERTaggedObject obj = (DERTaggedObject)enc; + setEncoding(obj.getTagNo()); + externalContent = obj.getObject(); + } + + /** + * Creates a new instance of DERExternal + * See X.690 for more informations about the meaning of these parameters + * @param directReference The direct reference or null if not set. + * @param indirectReference The indirect reference or null if not set. + * @param dataValueDescriptor The data value descriptor or null if not set. + * @param externalData The external data in its encoded form. + */ + public DERExternal(DERObjectIdentifier directReference, DERInteger indirectReference, ASN1Object dataValueDescriptor, DERTaggedObject externalData) + { + this(directReference, indirectReference, dataValueDescriptor, externalData.getTagNo(), externalData.getDERObject()); + } + + /** + * Creates a new instance of DERExternal. + * See X.690 for more informations about the meaning of these parameters + * @param directReference The direct reference or null if not set. + * @param indirectReference The indirect reference or null if not set. + * @param dataValueDescriptor The data value descriptor or null if not set. + * @param encoding The encoding to be used for the external data + * @param externalData The external data + */ + public DERExternal(DERObjectIdentifier directReference, DERInteger indirectReference, ASN1Object dataValueDescriptor, int encoding, DERObject externalData) + { + setDirectReference(directReference); + setIndirectReference(indirectReference); + setDataValueDescriptor(dataValueDescriptor); + setEncoding(encoding); + setExternalContent(externalData.getDERObject()); + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + public int hashCode() + { + int ret = 0; + if (directReference != null) + { + ret = directReference.hashCode(); + } + if (indirectReference != null) + { + ret ^= indirectReference.hashCode(); + } + if (dataValueDescriptor != null) + { + ret ^= dataValueDescriptor.hashCode(); + } + ret ^= externalContent.hashCode(); + return ret; + } + + /* (non-Javadoc) + * @see org.bouncycastle.asn1.DERObject#encode(org.bouncycastle.asn1.DEROutputStream) + */ + void encode(DEROutputStream out) + throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + if (directReference != null) + { + baos.write(directReference.getDEREncoded()); + } + if (indirectReference != null) + { + baos.write(indirectReference.getDEREncoded()); + } + if (dataValueDescriptor != null) + { + baos.write(dataValueDescriptor.getDEREncoded()); + } + DERTaggedObject obj = new DERTaggedObject(encoding, externalContent); + baos.write(obj.getDEREncoded()); + out.writeEncoded(DERTags.CONSTRUCTED, DERTags.EXTERNAL, baos.toByteArray()); + } + + /* (non-Javadoc) + * @see org.bouncycastle.asn1.ASN1Object#asn1Equals(org.bouncycastle.asn1.DERObject) + */ + boolean asn1Equals(DERObject o) + { + if (!(o instanceof DERExternal)) + { + return false; + } + if (this == o) + { + return true; + } + DERExternal other = (DERExternal)o; + if (directReference != null) + { + if (other.directReference == null || !other.directReference.equals(directReference)) + { + return false; + } + } + if (indirectReference != null) + { + if (other.indirectReference == null || !other.indirectReference.equals(indirectReference)) + { + return false; + } + } + if (dataValueDescriptor != null) + { + if (other.dataValueDescriptor == null || !other.dataValueDescriptor.equals(dataValueDescriptor)) + { + return false; + } + } + return externalContent.equals(other.externalContent); + } + + /** + * Returns the data value descriptor + * @return The descriptor + */ + public ASN1Object getDataValueDescriptor() + { + return dataValueDescriptor; + } + + /** + * Returns the direct reference of the external element + * @return The reference + */ + public DERObjectIdentifier getDirectReference() + { + return directReference; + } + + /** + * Returns the encoding of the content. Valid values are + *

    + *
  • 0 single-ASN1-type
  • + *
  • 1 OCTET STRING
  • + *
  • 2 BIT STRING
  • + *
+ * @return The encoding + */ + public int getEncoding() + { + return encoding; + } + + /** + * Returns the content of this element + * @return The content + */ + public DERObject getExternalContent() + { + return externalContent; + } + + /** + * Returns the indirect reference of this element + * @return The reference + */ + public DERInteger getIndirectReference() + { + return indirectReference; + } + + /** + * Sets the data value descriptor + * @param dataValueDescriptor The descriptor + */ + private void setDataValueDescriptor(ASN1Object dataValueDescriptor) + { + this.dataValueDescriptor = dataValueDescriptor; + } + + /** + * Sets the direct reference of the external element + * @param directReferemce The reference + */ + private void setDirectReference(DERObjectIdentifier directReferemce) + { + this.directReference = directReferemce; + } + + /** + * Sets the encoding of the content. Valid values are + *
    + *
  • 0 single-ASN1-type
  • + *
  • 1 OCTET STRING
  • + *
  • 2 BIT STRING
  • + *
+ * @param encoding The encoding + */ + private void setEncoding(int encoding) + { + if (encoding < 0 || encoding > 2) + { + throw new IllegalArgumentException("invalid encoding value: " + encoding); + } + this.encoding = encoding; + } + + /** + * Sets the content of this element + * @param externalContent The content + */ + private void setExternalContent(DERObject externalContent) + { + this.externalContent = externalContent; + } + + /** + * Sets the indirect reference of this element + * @param indirectReference The reference + */ + private void setIndirectReference(DERInteger indirectReference) + { + this.indirectReference = indirectReference; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERExternalParser.java b/src/com/google/bitcoin/bouncycastle/asn1/DERExternalParser.java new file mode 100644 index 000000000..8e115e2ce --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERExternalParser.java @@ -0,0 +1,39 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +public class DERExternalParser + implements DEREncodable +{ + private ASN1StreamParser _parser; + + /** + * + */ + public DERExternalParser(ASN1StreamParser parser) + { + this._parser = parser; + } + + public DEREncodable readObject() + throws IOException + { + return _parser.readObject(); + } + + public DERObject getDERObject() + { + try + { + return new DERExternal(_parser.readVector()); + } + catch (IOException ioe) + { + throw new ASN1ParsingException("unable to get DER object", ioe); + } + catch (IllegalArgumentException ioe) + { + throw new ASN1ParsingException("unable to get DER object", ioe); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERFactory.java b/src/com/google/bitcoin/bouncycastle/asn1/DERFactory.java new file mode 100644 index 000000000..83c1cbb47 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERFactory.java @@ -0,0 +1,22 @@ +package com.google.bitcoin.bouncycastle.asn1; + +class DERFactory +{ + static final DERSequence EMPTY_SEQUENCE = new DERSequence(); + static final DERSet EMPTY_SET = new DERSet(); + + static DERSequence createSequence(ASN1EncodableVector v) + { + return v.size() < 1 ? EMPTY_SEQUENCE : new DERSequence(v); + } + + static DERSet createSet(ASN1EncodableVector v) + { + return v.size() < 1 ? EMPTY_SET : new DERSet(v); + } + + static DERSet createSet(ASN1EncodableVector v, boolean needsSorting) + { + return v.size() < 1 ? EMPTY_SET : new DERSet(v, needsSorting); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERGeneralString.java b/src/com/google/bitcoin/bouncycastle/asn1/DERGeneralString.java new file mode 100644 index 000000000..ba62ce6a8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERGeneralString.java @@ -0,0 +1,92 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +public class DERGeneralString + extends ASN1Object implements DERString +{ + private String string; + + public static DERGeneralString getInstance( + Object obj) + { + if (obj == null || obj instanceof DERGeneralString) + { + return (DERGeneralString) obj; + } + if (obj instanceof ASN1OctetString) + { + return new DERGeneralString(((ASN1OctetString) obj).getOctets()); + } + if (obj instanceof ASN1TaggedObject) + { + return getInstance(((ASN1TaggedObject) obj).getObject()); + } + throw new IllegalArgumentException("illegal object in getInstance: " + + obj.getClass().getName()); + } + + public static DERGeneralString getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + public DERGeneralString(byte[] string) + { + char[] cs = new char[string.length]; + for (int i = 0; i != cs.length; i++) + { + cs[i] = (char)(string[i] & 0xff); + } + this.string = new String(cs); + } + + public DERGeneralString(String string) + { + this.string = string; + } + + public String getString() + { + return string; + } + + public String toString() + { + return string; + } + + public byte[] getOctets() + { + char[] cs = string.toCharArray(); + byte[] bs = new byte[cs.length]; + for (int i = 0; i != cs.length; i++) + { + bs[i] = (byte) cs[i]; + } + return bs; + } + + void encode(DEROutputStream out) + throws IOException + { + out.writeEncoded(GENERAL_STRING, this.getOctets()); + } + + public int hashCode() + { + return this.getString().hashCode(); + } + + boolean asn1Equals(DERObject o) + { + if (!(o instanceof DERGeneralString)) + { + return false; + } + DERGeneralString s = (DERGeneralString) o; + return this.getString().equals(s.getString()); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERGeneralizedTime.java b/src/com/google/bitcoin/bouncycastle/asn1/DERGeneralizedTime.java new file mode 100644 index 000000000..cc7c36bcf --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERGeneralizedTime.java @@ -0,0 +1,314 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.SimpleTimeZone; +import java.util.TimeZone; + +/** + * Generalized time object. + */ +public class DERGeneralizedTime + extends ASN1Object +{ + String time; + + /** + * return a generalized time from the passed in object + * + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static DERGeneralizedTime getInstance( + Object obj) + { + if (obj == null || obj instanceof DERGeneralizedTime) + { + return (DERGeneralizedTime)obj; + } + + if (obj instanceof ASN1OctetString) + { + return new DERGeneralizedTime(((ASN1OctetString)obj).getOctets()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * return a Generalized Time object from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static DERGeneralizedTime getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + /** + * The correct format for this is YYYYMMDDHHMMSS[.f]Z, or without the Z + * for local time, or Z+-HHMM on the end, for difference between local + * time and UTC time. The fractional second amount f must consist of at + * least one number with trailing zeroes removed. + * + * @param time the time string. + * @exception IllegalArgumentException if String is an illegal format. + */ + public DERGeneralizedTime( + String time) + { + this.time = time; + try + { + this.getDate(); + } + catch (ParseException e) + { + throw new IllegalArgumentException("invalid date string: " + e.getMessage()); + } + } + + /** + * base constructer from a java.util.date object + */ + public DERGeneralizedTime( + Date time) + { + SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmss'Z'"); + + dateF.setTimeZone(new SimpleTimeZone(0,"Z")); + + this.time = dateF.format(time); + } + + DERGeneralizedTime( + byte[] bytes) + { + // + // explicitly convert to characters + // + char[] dateC = new char[bytes.length]; + + for (int i = 0; i != dateC.length; i++) + { + dateC[i] = (char)(bytes[i] & 0xff); + } + + this.time = new String(dateC); + } + + /** + * Return the time. + * @return The time string as it appeared in the encoded object. + */ + public String getTimeString() + { + return time; + } + + /** + * return the time - always in the form of + * YYYYMMDDhhmmssGMT(+hh:mm|-hh:mm). + *

+ * Normally in a certificate we would expect "Z" rather than "GMT", + * however adding the "GMT" means we can just use: + *

+     *     dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
+     * 
+ * To read in the time and get a date which is compatible with our local + * time zone. + */ + public String getTime() + { + // + // standardise the format. + // + if (time.charAt(time.length() - 1) == 'Z') + { + return time.substring(0, time.length() - 1) + "GMT+00:00"; + } + else + { + int signPos = time.length() - 5; + char sign = time.charAt(signPos); + if (sign == '-' || sign == '+') + { + return time.substring(0, signPos) + + "GMT" + + time.substring(signPos, signPos + 3) + + ":" + + time.substring(signPos + 3); + } + else + { + signPos = time.length() - 3; + sign = time.charAt(signPos); + if (sign == '-' || sign == '+') + { + return time.substring(0, signPos) + + "GMT" + + time.substring(signPos) + + ":00"; + } + } + } + return time + calculateGMTOffset(); + } + + private String calculateGMTOffset() + { + String sign = "+"; + TimeZone timeZone = TimeZone.getDefault(); + int offset = timeZone.getRawOffset(); + if (offset < 0) + { + sign = "-"; + offset = -offset; + } + int hours = offset / (60 * 60 * 1000); + int minutes = (offset - (hours * 60 * 60 * 1000)) / (60 * 1000); + + try + { + if (timeZone.useDaylightTime() && timeZone.inDaylightTime(this.getDate())) + { + hours += sign.equals("+") ? 1 : -1; + } + } + catch (ParseException e) + { + // we'll do our best and ignore daylight savings + } + + return "GMT" + sign + convert(hours) + ":" + convert(minutes); + } + + private String convert(int time) + { + if (time < 10) + { + return "0" + time; + } + + return Integer.toString(time); + } + + public Date getDate() + throws ParseException + { + SimpleDateFormat dateF; + String d = time; + + if (time.endsWith("Z")) + { + if (hasFractionalSeconds()) + { + dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSS'Z'"); + } + else + { + dateF = new SimpleDateFormat("yyyyMMddHHmmss'Z'"); + } + + dateF.setTimeZone(new SimpleTimeZone(0, "Z")); + } + else if (time.indexOf('-') > 0 || time.indexOf('+') > 0) + { + d = this.getTime(); + if (hasFractionalSeconds()) + { + dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSSz"); + } + else + { + dateF = new SimpleDateFormat("yyyyMMddHHmmssz"); + } + + dateF.setTimeZone(new SimpleTimeZone(0, "Z")); + } + else + { + if (hasFractionalSeconds()) + { + dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSS"); + } + else + { + dateF = new SimpleDateFormat("yyyyMMddHHmmss"); + } + + dateF.setTimeZone(new SimpleTimeZone(0, TimeZone.getDefault().getID())); + } + + if (hasFractionalSeconds()) + { + // java misinterprets extra digits as being milliseconds... + String frac = d.substring(14); + int index; + for (index = 1; index < frac.length(); index++) + { + char ch = frac.charAt(index); + if (!('0' <= ch && ch <= '9')) + { + break; + } + } + if (index - 1 > 3) + { + frac = frac.substring(0, 4) + frac.substring(index); + d = d.substring(0, 14) + frac; + } + } + + return dateF.parse(d); + } + + private boolean hasFractionalSeconds() + { + return time.indexOf('.') == 14; + } + + private byte[] getOctets() + { + char[] cs = time.toCharArray(); + byte[] bs = new byte[cs.length]; + + for (int i = 0; i != cs.length; i++) + { + bs[i] = (byte)cs[i]; + } + + return bs; + } + + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(GENERALIZED_TIME, this.getOctets()); + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof DERGeneralizedTime)) + { + return false; + } + + return time.equals(((DERGeneralizedTime)o).time); + } + + public int hashCode() + { + return time.hashCode(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERGenerator.java b/src/com/google/bitcoin/bouncycastle/asn1/DERGenerator.java new file mode 100644 index 000000000..73dc6f74f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERGenerator.java @@ -0,0 +1,119 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import com.google.bitcoin.bouncycastle.util.io.Streams; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public abstract class DERGenerator + extends ASN1Generator +{ + private boolean _tagged = false; + private boolean _isExplicit; + private int _tagNo; + + protected DERGenerator( + OutputStream out) + { + super(out); + } + + public DERGenerator( + OutputStream out, + int tagNo, + boolean isExplicit) + { + super(out); + + _tagged = true; + _isExplicit = isExplicit; + _tagNo = tagNo; + } + + private void writeLength( + OutputStream out, + int length) + throws IOException + { + if (length > 127) + { + int size = 1; + int val = length; + + while ((val >>>= 8) != 0) + { + size++; + } + + out.write((byte)(size | 0x80)); + + for (int i = (size - 1) * 8; i >= 0; i -= 8) + { + out.write((byte)(length >> i)); + } + } + else + { + out.write((byte)length); + } + } + + void writeDEREncoded( + OutputStream out, + int tag, + byte[] bytes) + throws IOException + { + out.write(tag); + writeLength(out, bytes.length); + out.write(bytes); + } + + void writeDEREncoded( + int tag, + byte[] bytes) + throws IOException + { + if (_tagged) + { + int tagNum = _tagNo | DERTags.TAGGED; + + if (_isExplicit) + { + int newTag = _tagNo | DERTags.CONSTRUCTED | DERTags.TAGGED; + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + writeDEREncoded(bOut, tag, bytes); + + writeDEREncoded(_out, newTag, bOut.toByteArray()); + } + else + { + if ((tag & DERTags.CONSTRUCTED) != 0) + { + writeDEREncoded(_out, tagNum | DERTags.CONSTRUCTED, bytes); + } + else + { + writeDEREncoded(_out, tagNum, bytes); + } + } + } + else + { + writeDEREncoded(_out, tag, bytes); + } + } + + void writeDEREncoded( + OutputStream out, + int tag, + InputStream in) + throws IOException + { + writeDEREncoded(out, tag, Streams.readAll(in)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERIA5String.java b/src/com/google/bitcoin/bouncycastle/asn1/DERIA5String.java new file mode 100644 index 000000000..850db0da5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERIA5String.java @@ -0,0 +1,174 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +/** + * DER IA5String object - this is an ascii string. + */ +public class DERIA5String + extends ASN1Object + implements DERString +{ + String string; + + /** + * return a IA5 string from the passed in object + * + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static DERIA5String getInstance( + Object obj) + { + if (obj == null || obj instanceof DERIA5String) + { + return (DERIA5String)obj; + } + + if (obj instanceof ASN1OctetString) + { + return new DERIA5String(((ASN1OctetString)obj).getOctets()); + } + + if (obj instanceof ASN1TaggedObject) + { + return getInstance(((ASN1TaggedObject)obj).getObject()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * return an IA5 String from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static DERIA5String getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + /** + * basic constructor - with bytes. + */ + public DERIA5String( + byte[] string) + { + char[] cs = new char[string.length]; + + for (int i = 0; i != cs.length; i++) + { + cs[i] = (char)(string[i] & 0xff); + } + + this.string = new String(cs); + } + + /** + * basic constructor - without validation. + */ + public DERIA5String( + String string) + { + this(string, false); + } + + /** + * Constructor with optional validation. + * + * @param string the base string to wrap. + * @param validate whether or not to check the string. + * @throws IllegalArgumentException if validate is true and the string + * contains characters that should not be in an IA5String. + */ + public DERIA5String( + String string, + boolean validate) + { + if (string == null) + { + throw new NullPointerException("string cannot be null"); + } + if (validate && !isIA5String(string)) + { + throw new IllegalArgumentException("string contains illegal characters"); + } + + this.string = string; + } + + public String getString() + { + return string; + } + + public String toString() + { + return string; + } + + public byte[] getOctets() + { + char[] cs = string.toCharArray(); + byte[] bs = new byte[cs.length]; + + for (int i = 0; i != cs.length; i++) + { + bs[i] = (byte)cs[i]; + } + + return bs; + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(IA5_STRING, this.getOctets()); + } + + public int hashCode() + { + return this.getString().hashCode(); + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof DERIA5String)) + { + return false; + } + + DERIA5String s = (DERIA5String)o; + + return this.getString().equals(s.getString()); + } + + /** + * return true if the passed in String can be represented without + * loss as an IA5String, false otherwise. + * + * @return true if in printable set, false otherwise. + */ + public static boolean isIA5String( + String str) + { + for (int i = str.length() - 1; i >= 0; i--) + { + char ch = str.charAt(i); + + if (ch > 0x007f) + { + return false; + } + } + + return true; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERInputStream.java b/src/com/google/bitcoin/bouncycastle/asn1/DERInputStream.java new file mode 100644 index 000000000..58043c8c0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERInputStream.java @@ -0,0 +1,272 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Don't use this class. It will eventually disappear, use ASN1InputStream. + *
+ * This class is scheduled for removal. + * @deprecated use ASN1InputStream + */ +public class DERInputStream + extends FilterInputStream implements DERTags +{ + /** + * @deprecated use ASN1InputStream + */ + public DERInputStream( + InputStream is) + { + super(is); + } + + protected int readLength() + throws IOException + { + int length = read(); + if (length < 0) + { + throw new IOException("EOF found when length expected"); + } + + if (length == 0x80) + { + return -1; // indefinite-length encoding + } + + if (length > 127) + { + int size = length & 0x7f; + + if (size > 4) + { + throw new IOException("DER length more than 4 bytes"); + } + + length = 0; + for (int i = 0; i < size; i++) + { + int next = read(); + + if (next < 0) + { + throw new IOException("EOF found reading length"); + } + + length = (length << 8) + next; + } + + if (length < 0) + { + throw new IOException("corrupted stream - negative length found"); + } + } + + return length; + } + + protected void readFully( + byte[] bytes) + throws IOException + { + int left = bytes.length; + + if (left == 0) + { + return; + } + + while (left > 0) + { + int l = read(bytes, bytes.length - left, left); + + if (l < 0) + { + throw new EOFException("unexpected end of stream"); + } + + left -= l; + } + } + + /** + * build an object given its tag and a byte stream to construct it + * from. + */ + protected DERObject buildObject( + int tag, + byte[] bytes) + throws IOException + { + switch (tag) + { + case NULL: + return null; + case SEQUENCE | CONSTRUCTED: + ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); + BERInputStream dIn = new BERInputStream(bIn); + DERConstructedSequence seq = new DERConstructedSequence(); + + try + { + for (;;) + { + DERObject obj = dIn.readObject(); + + seq.addObject(obj); + } + } + catch (EOFException ex) + { + return seq; + } + case SET | CONSTRUCTED: + bIn = new ByteArrayInputStream(bytes); + dIn = new BERInputStream(bIn); + + ASN1EncodableVector v = new ASN1EncodableVector(); + + try + { + for (;;) + { + DERObject obj = dIn.readObject(); + + v.add(obj); + } + } + catch (EOFException ex) + { + return new DERConstructedSet(v); + } + case BOOLEAN: + return new DERBoolean(bytes); + case INTEGER: + return new DERInteger(bytes); + case ENUMERATED: + return new DEREnumerated(bytes); + case OBJECT_IDENTIFIER: + return new DERObjectIdentifier(bytes); + case BIT_STRING: + int padBits = bytes[0]; + byte[] data = new byte[bytes.length - 1]; + + System.arraycopy(bytes, 1, data, 0, bytes.length - 1); + + return new DERBitString(data, padBits); + case UTF8_STRING: + return new DERUTF8String(bytes); + case PRINTABLE_STRING: + return new DERPrintableString(bytes); + case IA5_STRING: + return new DERIA5String(bytes); + case T61_STRING: + return new DERT61String(bytes); + case VISIBLE_STRING: + return new DERVisibleString(bytes); + case UNIVERSAL_STRING: + return new DERUniversalString(bytes); + case GENERAL_STRING: + return new DERGeneralString(bytes); + case BMP_STRING: + return new DERBMPString(bytes); + case OCTET_STRING: + return new DEROctetString(bytes); + case UTC_TIME: + return new DERUTCTime(bytes); + case GENERALIZED_TIME: + return new DERGeneralizedTime(bytes); + default: + // + // with tagged object tag number is bottom 5 bits + // + if ((tag & TAGGED) != 0) + { + if ((tag & 0x1f) == 0x1f) + { + throw new IOException("unsupported high tag encountered"); + } + + if (bytes.length == 0) // empty tag! + { + if ((tag & CONSTRUCTED) == 0) + { + return new DERTaggedObject(false, tag & 0x1f, new DERNull()); + } + else + { + return new DERTaggedObject(false, tag & 0x1f, new DERConstructedSequence()); + } + } + + // + // simple type - implicit... return an octet string + // + if ((tag & CONSTRUCTED) == 0) + { + return new DERTaggedObject(false, tag & 0x1f, new DEROctetString(bytes)); + } + + bIn = new ByteArrayInputStream(bytes); + dIn = new BERInputStream(bIn); + + DEREncodable dObj = dIn.readObject(); + + // + // explicitly tagged (probably!) - if it isn't we'd have to + // tell from the context + // + if (dIn.available() == 0) + { + return new DERTaggedObject(tag & 0x1f, dObj); + } + + // + // another implicit object, we'll create a sequence... + // + seq = new DERConstructedSequence(); + + seq.addObject(dObj); + + try + { + for (;;) + { + dObj = dIn.readObject(); + + seq.addObject(dObj); + } + } + catch (EOFException ex) + { + // ignore -- + } + + return new DERTaggedObject(false, tag & 0x1f, seq); + } + + return new DERUnknownTag(tag, bytes); + } + } + + public DERObject readObject() + throws IOException + { + int tag = read(); + if (tag == -1) + { + throw new EOFException(); + } + + int length = readLength(); + byte[] bytes = new byte[length]; + + readFully(bytes); + + return buildObject(tag, bytes); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERInteger.java b/src/com/google/bitcoin/bouncycastle/asn1/DERInteger.java new file mode 100644 index 000000000..05105ca4a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERInteger.java @@ -0,0 +1,123 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.util.Arrays; + +public class DERInteger + extends ASN1Object +{ + byte[] bytes; + + /** + * return an integer from the passed in object + * + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static DERInteger getInstance( + Object obj) + { + if (obj == null || obj instanceof DERInteger) + { + return (DERInteger)obj; + } + + if (obj instanceof ASN1OctetString) + { + return new DERInteger(((ASN1OctetString)obj).getOctets()); + } + + if (obj instanceof ASN1TaggedObject) + { + return getInstance(((ASN1TaggedObject)obj).getObject()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * return an Integer from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static DERInteger getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + public DERInteger( + int value) + { + bytes = BigInteger.valueOf(value).toByteArray(); + } + + public DERInteger( + BigInteger value) + { + bytes = value.toByteArray(); + } + + public DERInteger( + byte[] bytes) + { + this.bytes = bytes; + } + + public BigInteger getValue() + { + return new BigInteger(bytes); + } + + /** + * in some cases positive values get crammed into a space, + * that's not quite big enough... + */ + public BigInteger getPositiveValue() + { + return new BigInteger(1, bytes); + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(INTEGER, bytes); + } + + public int hashCode() + { + int value = 0; + + for (int i = 0; i != bytes.length; i++) + { + value ^= (bytes[i] & 0xff) << (i % 4); + } + + return value; + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof DERInteger)) + { + return false; + } + + DERInteger other = (DERInteger)o; + + return Arrays.areEqual(bytes, other.bytes); + } + + public String toString() + { + return getValue().toString(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERNull.java b/src/com/google/bitcoin/bouncycastle/asn1/DERNull.java new file mode 100644 index 000000000..9cb101c66 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERNull.java @@ -0,0 +1,25 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +/** + * A NULL object. + */ +public class DERNull + extends ASN1Null +{ + public static final DERNull INSTANCE = new DERNull(); + + byte[] zeroBytes = new byte[0]; + + public DERNull() + { + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(NULL, zeroBytes); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERNumericString.java b/src/com/google/bitcoin/bouncycastle/asn1/DERNumericString.java new file mode 100644 index 000000000..05de13f0e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERNumericString.java @@ -0,0 +1,177 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +/** + * DER NumericString object - this is an ascii string of characters {0,1,2,3,4,5,6,7,8,9, }. + */ +public class DERNumericString + extends ASN1Object + implements DERString +{ + String string; + + /** + * return a Numeric string from the passed in object + * + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static DERNumericString getInstance( + Object obj) + { + if (obj == null || obj instanceof DERNumericString) + { + return (DERNumericString)obj; + } + + if (obj instanceof ASN1OctetString) + { + return new DERNumericString(((ASN1OctetString)obj).getOctets()); + } + + if (obj instanceof ASN1TaggedObject) + { + return getInstance(((ASN1TaggedObject)obj).getObject()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * return an Numeric String from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static DERNumericString getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + /** + * basic constructor - with bytes. + */ + public DERNumericString( + byte[] string) + { + char[] cs = new char[string.length]; + + for (int i = 0; i != cs.length; i++) + { + cs[i] = (char)(string[i] & 0xff); + } + + this.string = new String(cs); + } + + /** + * basic constructor - without validation.. + */ + public DERNumericString( + String string) + { + this(string, false); + } + + /** + * Constructor with optional validation. + * + * @param string the base string to wrap. + * @param validate whether or not to check the string. + * @throws IllegalArgumentException if validate is true and the string + * contains characters that should not be in a NumericString. + */ + public DERNumericString( + String string, + boolean validate) + { + if (validate && !isNumericString(string)) + { + throw new IllegalArgumentException("string contains illegal characters"); + } + + this.string = string; + } + + public String getString() + { + return string; + } + + public String toString() + { + return string; + } + + public byte[] getOctets() + { + char[] cs = string.toCharArray(); + byte[] bs = new byte[cs.length]; + + for (int i = 0; i != cs.length; i++) + { + bs[i] = (byte)cs[i]; + } + + return bs; + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(NUMERIC_STRING, this.getOctets()); + } + + public int hashCode() + { + return this.getString().hashCode(); + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof DERNumericString)) + { + return false; + } + + DERNumericString s = (DERNumericString)o; + + return this.getString().equals(s.getString()); + } + + /** + * Return true if the string can be represented as a NumericString ('0'..'9', ' ') + * + * @param str string to validate. + * @return true if numeric, fale otherwise. + */ + public static boolean isNumericString( + String str) + { + for (int i = str.length() - 1; i >= 0; i--) + { + char ch = str.charAt(i); + + if (ch > 0x007f) + { + return false; + } + + if (('0' <= ch && ch <= '9') || ch == ' ') + { + continue; + } + + return false; + } + + return true; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERObject.java b/src/com/google/bitcoin/bouncycastle/asn1/DERObject.java new file mode 100644 index 000000000..bcd4fb73d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERObject.java @@ -0,0 +1,20 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +public abstract class DERObject + extends ASN1Encodable + implements DERTags +{ + public DERObject toASN1Object() + { + return this; + } + + public abstract int hashCode(); + + public abstract boolean equals(Object o); + + abstract void encode(DEROutputStream out) + throws IOException; +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERObjectIdentifier.java b/src/com/google/bitcoin/bouncycastle/asn1/DERObjectIdentifier.java new file mode 100644 index 000000000..10c53b6c5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERObjectIdentifier.java @@ -0,0 +1,293 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; + +public class DERObjectIdentifier + extends ASN1Object +{ + String identifier; + + /** + * return an OID from the passed in object + * + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static DERObjectIdentifier getInstance( + Object obj) + { + if (obj == null || obj instanceof DERObjectIdentifier) + { + return (DERObjectIdentifier)obj; + } + + if (obj instanceof ASN1OctetString) + { + return new DERObjectIdentifier(((ASN1OctetString)obj).getOctets()); + } + + if (obj instanceof ASN1TaggedObject) + { + return getInstance(((ASN1TaggedObject)obj).getObject()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * return an Object Identifier from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static DERObjectIdentifier getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + + DERObjectIdentifier( + byte[] bytes) + { + StringBuffer objId = new StringBuffer(); + long value = 0; + BigInteger bigValue = null; + boolean first = true; + + for (int i = 0; i != bytes.length; i++) + { + int b = bytes[i] & 0xff; + + if (value < 0x80000000000000L) + { + value = value * 128 + (b & 0x7f); + if ((b & 0x80) == 0) // end of number reached + { + if (first) + { + switch ((int)value / 40) + { + case 0: + objId.append('0'); + break; + case 1: + objId.append('1'); + value -= 40; + break; + default: + objId.append('2'); + value -= 80; + } + first = false; + } + + objId.append('.'); + objId.append(value); + value = 0; + } + } + else + { + if (bigValue == null) + { + bigValue = BigInteger.valueOf(value); + } + bigValue = bigValue.shiftLeft(7); + bigValue = bigValue.or(BigInteger.valueOf(b & 0x7f)); + if ((b & 0x80) == 0) + { + objId.append('.'); + objId.append(bigValue); + bigValue = null; + value = 0; + } + } + } + + this.identifier = objId.toString(); + } + + public DERObjectIdentifier( + String identifier) + { + if (!isValidIdentifier(identifier)) + { + throw new IllegalArgumentException("string " + identifier + " not an OID"); + } + + this.identifier = identifier; + } + + public String getId() + { + return identifier; + } + + private void writeField( + OutputStream out, + long fieldValue) + throws IOException + { + if (fieldValue >= (1L << 7)) + { + if (fieldValue >= (1L << 14)) + { + if (fieldValue >= (1L << 21)) + { + if (fieldValue >= (1L << 28)) + { + if (fieldValue >= (1L << 35)) + { + if (fieldValue >= (1L << 42)) + { + if (fieldValue >= (1L << 49)) + { + if (fieldValue >= (1L << 56)) + { + out.write((int)(fieldValue >> 56) | 0x80); + } + out.write((int)(fieldValue >> 49) | 0x80); + } + out.write((int)(fieldValue >> 42) | 0x80); + } + out.write((int)(fieldValue >> 35) | 0x80); + } + out.write((int)(fieldValue >> 28) | 0x80); + } + out.write((int)(fieldValue >> 21) | 0x80); + } + out.write((int)(fieldValue >> 14) | 0x80); + } + out.write((int)(fieldValue >> 7) | 0x80); + } + out.write((int)fieldValue & 0x7f); + } + + private void writeField( + OutputStream out, + BigInteger fieldValue) + throws IOException + { + int byteCount = (fieldValue.bitLength()+6)/7; + if (byteCount == 0) + { + out.write(0); + } + else + { + BigInteger tmpValue = fieldValue; + byte[] tmp = new byte[byteCount]; + for (int i = byteCount-1; i >= 0; i--) + { + tmp[i] = (byte) ((tmpValue.intValue() & 0x7f) | 0x80); + tmpValue = tmpValue.shiftRight(7); + } + tmp[byteCount-1] &= 0x7f; + out.write(tmp); + } + + } + + void encode( + DEROutputStream out) + throws IOException + { + OIDTokenizer tok = new OIDTokenizer(identifier); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + DEROutputStream dOut = new DEROutputStream(bOut); + + writeField(bOut, + Integer.parseInt(tok.nextToken()) * 40 + + Integer.parseInt(tok.nextToken())); + + while (tok.hasMoreTokens()) + { + String token = tok.nextToken(); + if (token.length() < 18) + { + writeField(bOut, Long.parseLong(token)); + } + else + { + writeField(bOut, new BigInteger(token)); + } + } + + dOut.close(); + + byte[] bytes = bOut.toByteArray(); + + out.writeEncoded(OBJECT_IDENTIFIER, bytes); + } + + public int hashCode() + { + return identifier.hashCode(); + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof DERObjectIdentifier)) + { + return false; + } + + return identifier.equals(((DERObjectIdentifier)o).identifier); + } + + public String toString() + { + return getId(); + } + + private static boolean isValidIdentifier( + String identifier) + { + if (identifier.length() < 3 + || identifier.charAt(1) != '.') + { + return false; + } + + char first = identifier.charAt(0); + if (first < '0' || first > '2') + { + return false; + } + + boolean periodAllowed = false; + for (int i = identifier.length() - 1; i >= 2; i--) + { + char ch = identifier.charAt(i); + + if ('0' <= ch && ch <= '9') + { + periodAllowed = true; + continue; + } + + if (ch == '.') + { + if (!periodAllowed) + { + return false; + } + + periodAllowed = false; + continue; + } + + return false; + } + + return periodAllowed; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DEROctetString.java b/src/com/google/bitcoin/bouncycastle/asn1/DEROctetString.java new file mode 100644 index 000000000..9cb15265f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DEROctetString.java @@ -0,0 +1,37 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +public class DEROctetString + extends ASN1OctetString +{ + /** + * @param string the octets making up the octet string. + */ + public DEROctetString( + byte[] string) + { + super(string); + } + + public DEROctetString( + DEREncodable obj) + { + super(obj); + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(OCTET_STRING, string); + } + + static void encode( + DEROutputStream derOut, + byte[] bytes) + throws IOException + { + derOut.writeEncoded(DERTags.OCTET_STRING, bytes); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DEROctetStringParser.java b/src/com/google/bitcoin/bouncycastle/asn1/DEROctetStringParser.java new file mode 100644 index 000000000..611e96685 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DEROctetStringParser.java @@ -0,0 +1,33 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.InputStream; +import java.io.IOException; + +public class DEROctetStringParser + implements ASN1OctetStringParser +{ + private DefiniteLengthInputStream stream; + + DEROctetStringParser( + DefiniteLengthInputStream stream) + { + this.stream = stream; + } + + public InputStream getOctetStream() + { + return stream; + } + + public DERObject getDERObject() + { + try + { + return new DEROctetString(stream.toByteArray()); + } + catch (IOException e) + { + throw new ASN1ParsingException("IOException converting stream to byte array: " + e.getMessage(), e); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DEROutputStream.java b/src/com/google/bitcoin/bouncycastle/asn1/DEROutputStream.java new file mode 100644 index 000000000..b85cf4a64 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DEROutputStream.java @@ -0,0 +1,134 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class DEROutputStream + extends FilterOutputStream implements DERTags +{ + public DEROutputStream( + OutputStream os) + { + super(os); + } + + private void writeLength( + int length) + throws IOException + { + if (length > 127) + { + int size = 1; + int val = length; + + while ((val >>>= 8) != 0) + { + size++; + } + + write((byte)(size | 0x80)); + + for (int i = (size - 1) * 8; i >= 0; i -= 8) + { + write((byte)(length >> i)); + } + } + else + { + write((byte)length); + } + } + + void writeEncoded( + int tag, + byte[] bytes) + throws IOException + { + write(tag); + writeLength(bytes.length); + write(bytes); + } + + void writeTag(int flags, int tagNo) + throws IOException + { + if (tagNo < 31) + { + write(flags | tagNo); + } + else + { + write(flags | 0x1f); + if (tagNo < 128) + { + write(tagNo); + } + else + { + byte[] stack = new byte[5]; + int pos = stack.length; + + stack[--pos] = (byte)(tagNo & 0x7F); + + do + { + tagNo >>= 7; + stack[--pos] = (byte)(tagNo & 0x7F | 0x80); + } + while (tagNo > 127); + + write(stack, pos, stack.length - pos); + } + } + } + + void writeEncoded(int flags, int tagNo, byte[] bytes) + throws IOException + { + writeTag(flags, tagNo); + writeLength(bytes.length); + write(bytes); + } + + protected void writeNull() + throws IOException + { + write(NULL); + write(0x00); + } + + public void write(byte[] buf) + throws IOException + { + out.write(buf, 0, buf.length); + } + + public void write(byte[] buf, int offSet, int len) + throws IOException + { + out.write(buf, offSet, len); + } + + public void writeObject( + Object obj) + throws IOException + { + if (obj == null) + { + writeNull(); + } + else if (obj instanceof DERObject) + { + ((DERObject)obj).encode(this); + } + else if (obj instanceof DEREncodable) + { + ((DEREncodable)obj).getDERObject().encode(this); + } + else + { + throw new IOException("object not DEREncodable"); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERPrintableString.java b/src/com/google/bitcoin/bouncycastle/asn1/DERPrintableString.java new file mode 100644 index 000000000..a989ec8c8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERPrintableString.java @@ -0,0 +1,204 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +/** + * DER PrintableString object. + */ +public class DERPrintableString + extends ASN1Object + implements DERString +{ + String string; + + /** + * return a printable string from the passed in object. + * + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static DERPrintableString getInstance( + Object obj) + { + if (obj == null || obj instanceof DERPrintableString) + { + return (DERPrintableString)obj; + } + + if (obj instanceof ASN1OctetString) + { + return new DERPrintableString(((ASN1OctetString)obj).getOctets()); + } + + if (obj instanceof ASN1TaggedObject) + { + return getInstance(((ASN1TaggedObject)obj).getObject()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * return a Printable String from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static DERPrintableString getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + /** + * basic constructor - byte encoded string. + */ + public DERPrintableString( + byte[] string) + { + char[] cs = new char[string.length]; + + for (int i = 0; i != cs.length; i++) + { + cs[i] = (char)(string[i] & 0xff); + } + + this.string = new String(cs); + } + + /** + * basic constructor - this does not validate the string + */ + public DERPrintableString( + String string) + { + this(string, false); + } + + /** + * Constructor with optional validation. + * + * @param string the base string to wrap. + * @param validate whether or not to check the string. + * @throws IllegalArgumentException if validate is true and the string + * contains characters that should not be in a PrintableString. + */ + public DERPrintableString( + String string, + boolean validate) + { + if (validate && !isPrintableString(string)) + { + throw new IllegalArgumentException("string contains illegal characters"); + } + + this.string = string; + } + + public String getString() + { + return string; + } + + public byte[] getOctets() + { + char[] cs = string.toCharArray(); + byte[] bs = new byte[cs.length]; + + for (int i = 0; i != cs.length; i++) + { + bs[i] = (byte)cs[i]; + } + + return bs; + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(PRINTABLE_STRING, this.getOctets()); + } + + public int hashCode() + { + return this.getString().hashCode(); + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof DERPrintableString)) + { + return false; + } + + DERPrintableString s = (DERPrintableString)o; + + return this.getString().equals(s.getString()); + } + + public String toString() + { + return string; + } + + /** + * return true if the passed in String can be represented without + * loss as a PrintableString, false otherwise. + * + * @return true if in printable set, false otherwise. + */ + public static boolean isPrintableString( + String str) + { + for (int i = str.length() - 1; i >= 0; i--) + { + char ch = str.charAt(i); + + if (ch > 0x007f) + { + return false; + } + + if ('a' <= ch && ch <= 'z') + { + continue; + } + + if ('A' <= ch && ch <= 'Z') + { + continue; + } + + if ('0' <= ch && ch <= '9') + { + continue; + } + + switch (ch) + { + case ' ': + case '\'': + case '(': + case ')': + case '+': + case '-': + case '.': + case ':': + case '=': + case '?': + case '/': + case ',': + continue; + } + + return false; + } + + return true; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERSequence.java b/src/com/google/bitcoin/bouncycastle/asn1/DERSequence.java new file mode 100644 index 000000000..5f5ce53a6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERSequence.java @@ -0,0 +1,80 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Enumeration; + +public class DERSequence + extends ASN1Sequence +{ + /** + * create an empty sequence + */ + public DERSequence() + { + } + + /** + * create a sequence containing one object + */ + public DERSequence( + DEREncodable obj) + { + this.addObject(obj); + } + + /** + * create a sequence containing a vector of objects. + */ + public DERSequence( + DEREncodableVector v) + { + for (int i = 0; i != v.size(); i++) + { + this.addObject(v.get(i)); + } + } + + /** + * create a sequence containing an array of objects. + */ + public DERSequence( + ASN1Encodable[] a) + { + for (int i = 0; i != a.length; i++) + { + this.addObject(a[i]); + } + } + + /* + * A note on the implementation: + *

+ * As DER requires the constructed, definite-length model to + * be used for structured types, this varies slightly from the + * ASN.1 descriptions given. Rather than just outputing SEQUENCE, + * we also have to specify CONSTRUCTED, and the objects length. + */ + void encode( + DEROutputStream out) + throws IOException + { + // TODO Intermediate buffer could be avoided if we could calculate expected length + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + DEROutputStream dOut = new DEROutputStream(bOut); + Enumeration e = this.getObjects(); + + while (e.hasMoreElements()) + { + Object obj = e.nextElement(); + + dOut.writeObject(obj); + } + + dOut.close(); + + byte[] bytes = bOut.toByteArray(); + + out.writeEncoded(SEQUENCE | CONSTRUCTED, bytes); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERSequenceGenerator.java b/src/com/google/bitcoin/bouncycastle/asn1/DERSequenceGenerator.java new file mode 100644 index 000000000..b19834636 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERSequenceGenerator.java @@ -0,0 +1,45 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class DERSequenceGenerator + extends DERGenerator +{ + private final ByteArrayOutputStream _bOut = new ByteArrayOutputStream(); + + public DERSequenceGenerator( + OutputStream out) + throws IOException + { + super(out); + } + + public DERSequenceGenerator( + OutputStream out, + int tagNo, + boolean isExplicit) + throws IOException + { + super(out, tagNo, isExplicit); + } + + public void addObject( + DEREncodable object) + throws IOException + { + object.getDERObject().encode(new DEROutputStream(_bOut)); + } + + public OutputStream getRawOutputStream() + { + return _bOut; + } + + public void close() + throws IOException + { + writeDEREncoded(DERTags.CONSTRUCTED | DERTags.SEQUENCE, _bOut.toByteArray()); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERSequenceParser.java b/src/com/google/bitcoin/bouncycastle/asn1/DERSequenceParser.java new file mode 100644 index 000000000..44465d72f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERSequenceParser.java @@ -0,0 +1,32 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +public class DERSequenceParser + implements ASN1SequenceParser +{ + private ASN1StreamParser _parser; + + DERSequenceParser(ASN1StreamParser parser) + { + this._parser = parser; + } + + public DEREncodable readObject() + throws IOException + { + return _parser.readObject(); + } + + public DERObject getDERObject() + { + try + { + return new DERSequence(_parser.readVector()); + } + catch (IOException e) + { + throw new IllegalStateException(e.getMessage()); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERSet.java b/src/com/google/bitcoin/bouncycastle/asn1/DERSet.java new file mode 100644 index 000000000..ca328746c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERSet.java @@ -0,0 +1,100 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Enumeration; + +/** + * A DER encoded set object + */ +public class DERSet + extends ASN1Set +{ + /** + * create an empty set + */ + public DERSet() + { + } + + /** + * @param obj - a single object that makes up the set. + */ + public DERSet( + DEREncodable obj) + { + this.addObject(obj); + } + + /** + * @param v - a vector of objects making up the set. + */ + public DERSet( + DEREncodableVector v) + { + this(v, true); + } + + /** + * create a set from an array of objects. + */ + public DERSet( + ASN1Encodable[] a) + { + for (int i = 0; i != a.length; i++) + { + this.addObject(a[i]); + } + + this.sort(); + } + + /** + * @param v - a vector of objects making up the set. + */ + DERSet( + DEREncodableVector v, + boolean needsSorting) + { + for (int i = 0; i != v.size(); i++) + { + this.addObject(v.get(i)); + } + + if (needsSorting) + { + this.sort(); + } + } + + /* + * A note on the implementation: + *

+ * As DER requires the constructed, definite-length model to + * be used for structured types, this varies slightly from the + * ASN.1 descriptions given. Rather than just outputing SET, + * we also have to specify CONSTRUCTED, and the objects length. + */ + void encode( + DEROutputStream out) + throws IOException + { + // TODO Intermediate buffer could be avoided if we could calculate expected length + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + DEROutputStream dOut = new DEROutputStream(bOut); + Enumeration e = this.getObjects(); + + while (e.hasMoreElements()) + { + Object obj = e.nextElement(); + + dOut.writeObject(obj); + } + + dOut.close(); + + byte[] bytes = bOut.toByteArray(); + + out.writeEncoded(SET | CONSTRUCTED, bytes); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERSetParser.java b/src/com/google/bitcoin/bouncycastle/asn1/DERSetParser.java new file mode 100644 index 000000000..ac8626b31 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERSetParser.java @@ -0,0 +1,32 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +public class DERSetParser + implements ASN1SetParser +{ + private ASN1StreamParser _parser; + + DERSetParser(ASN1StreamParser parser) + { + this._parser = parser; + } + + public DEREncodable readObject() + throws IOException + { + return _parser.readObject(); + } + + public DERObject getDERObject() + { + try + { + return new DERSet(_parser.readVector(), false); + } + catch (IOException e) + { + throw new ASN1ParsingException(e.getMessage(), e); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERString.java b/src/com/google/bitcoin/bouncycastle/asn1/DERString.java new file mode 100644 index 000000000..cd8b9615c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERString.java @@ -0,0 +1,9 @@ +package com.google.bitcoin.bouncycastle.asn1; + +/** + * basic interface for DER string objects. + */ +public interface DERString +{ + public String getString(); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERT61String.java b/src/com/google/bitcoin/bouncycastle/asn1/DERT61String.java new file mode 100644 index 000000000..3733dbd68 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERT61String.java @@ -0,0 +1,126 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +/** + * DER T61String (also the teletex string) + */ +public class DERT61String + extends ASN1Object + implements DERString +{ + String string; + + /** + * return a T61 string from the passed in object. + * + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static DERT61String getInstance( + Object obj) + { + if (obj == null || obj instanceof DERT61String) + { + return (DERT61String)obj; + } + + if (obj instanceof ASN1OctetString) + { + return new DERT61String(((ASN1OctetString)obj).getOctets()); + } + + if (obj instanceof ASN1TaggedObject) + { + return getInstance(((ASN1TaggedObject)obj).getObject()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * return an T61 String from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static DERT61String getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + /** + * basic constructor - with bytes. + */ + public DERT61String( + byte[] string) + { + char[] cs = new char[string.length]; + + for (int i = 0; i != cs.length; i++) + { + cs[i] = (char)(string[i] & 0xff); + } + + this.string = new String(cs); + } + + /** + * basic constructor - with string. + */ + public DERT61String( + String string) + { + this.string = string; + } + + public String getString() + { + return string; + } + + public String toString() + { + return string; + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(T61_STRING, this.getOctets()); + } + + public byte[] getOctets() + { + char[] cs = string.toCharArray(); + byte[] bs = new byte[cs.length]; + + for (int i = 0; i != cs.length; i++) + { + bs[i] = (byte)cs[i]; + } + + return bs; + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof DERT61String)) + { + return false; + } + + return this.getString().equals(((DERT61String)o).getString()); + } + + public int hashCode() + { + return this.getString().hashCode(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERTaggedObject.java b/src/com/google/bitcoin/bouncycastle/asn1/DERTaggedObject.java new file mode 100644 index 000000000..7ddc7ee4a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERTaggedObject.java @@ -0,0 +1,85 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +/** + * DER TaggedObject - in ASN.1 notation this is any object preceded by + * a [n] where n is some number - these are assumed to follow the construction + * rules (as with sequences). + */ +public class DERTaggedObject + extends ASN1TaggedObject +{ + private static final byte[] ZERO_BYTES = new byte[0]; + + /** + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + public DERTaggedObject( + int tagNo, + DEREncodable obj) + { + super(tagNo, obj); + } + + /** + * @param explicit true if an explicitly tagged object. + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + public DERTaggedObject( + boolean explicit, + int tagNo, + DEREncodable obj) + { + super(explicit, tagNo, obj); + } + + /** + * create an implicitly tagged object that contains a zero + * length sequence. + */ + public DERTaggedObject( + int tagNo) + { + super(false, tagNo, new DERSequence()); + } + + void encode( + DEROutputStream out) + throws IOException + { + if (!empty) + { + byte[] bytes = obj.getDERObject().getEncoded(DER); + + if (explicit) + { + out.writeEncoded(CONSTRUCTED | TAGGED, tagNo, bytes); + } + else + { + // + // need to mark constructed types... + // + int flags; + if ((bytes[0] & CONSTRUCTED) != 0) + { + flags = CONSTRUCTED | TAGGED; + } + else + { + flags = TAGGED; + } + + out.writeTag(flags, tagNo); + out.write(bytes, 1, bytes.length - 1); + } + } + else + { + out.writeEncoded(CONSTRUCTED | TAGGED, tagNo, ZERO_BYTES); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERTags.java b/src/com/google/bitcoin/bouncycastle/asn1/DERTags.java new file mode 100644 index 000000000..1a6e0abac --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERTags.java @@ -0,0 +1,36 @@ +package com.google.bitcoin.bouncycastle.asn1; + +public interface DERTags +{ + public static final int BOOLEAN = 0x01; + public static final int INTEGER = 0x02; + public static final int BIT_STRING = 0x03; + public static final int OCTET_STRING = 0x04; + public static final int NULL = 0x05; + public static final int OBJECT_IDENTIFIER = 0x06; + public static final int EXTERNAL = 0x08; + public static final int ENUMERATED = 0x0a; + public static final int SEQUENCE = 0x10; + public static final int SEQUENCE_OF = 0x10; // for completeness + public static final int SET = 0x11; + public static final int SET_OF = 0x11; // for completeness + + + public static final int NUMERIC_STRING = 0x12; + public static final int PRINTABLE_STRING = 0x13; + public static final int T61_STRING = 0x14; + public static final int VIDEOTEX_STRING = 0x15; + public static final int IA5_STRING = 0x16; + public static final int UTC_TIME = 0x17; + public static final int GENERALIZED_TIME = 0x18; + public static final int GRAPHIC_STRING = 0x19; + public static final int VISIBLE_STRING = 0x1a; + public static final int GENERAL_STRING = 0x1b; + public static final int UNIVERSAL_STRING = 0x1c; + public static final int BMP_STRING = 0x1e; + public static final int UTF8_STRING = 0x0c; + + public static final int CONSTRUCTED = 0x20; + public static final int APPLICATION = 0x40; + public static final int TAGGED = 0x80; +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERUTCTime.java b/src/com/google/bitcoin/bouncycastle/asn1/DERUTCTime.java new file mode 100644 index 000000000..b599572d8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERUTCTime.java @@ -0,0 +1,254 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.SimpleTimeZone; + +/** + * UTC time object. + */ +public class DERUTCTime + extends ASN1Object +{ + String time; + + /** + * return an UTC Time from the passed in object. + * + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static DERUTCTime getInstance( + Object obj) + { + if (obj == null || obj instanceof DERUTCTime) + { + return (DERUTCTime)obj; + } + + if (obj instanceof ASN1OctetString) + { + return new DERUTCTime(((ASN1OctetString)obj).getOctets()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * return an UTC Time from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static DERUTCTime getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + /** + * The correct format for this is YYMMDDHHMMSSZ (it used to be that seconds were + * never encoded. When you're creating one of these objects from scratch, that's + * what you want to use, otherwise we'll try to deal with whatever gets read from + * the input stream... (this is why the input format is different from the getTime() + * method output). + *

+ * + * @param time the time string. + */ + public DERUTCTime( + String time) + { + this.time = time; + try + { + this.getDate(); + } + catch (ParseException e) + { + throw new IllegalArgumentException("invalid date string: " + e.getMessage()); + } + } + + /** + * base constructer from a java.util.date object + */ + public DERUTCTime( + Date time) + { + SimpleDateFormat dateF = new SimpleDateFormat("yyMMddHHmmss'Z'"); + + dateF.setTimeZone(new SimpleTimeZone(0,"Z")); + + this.time = dateF.format(time); + } + + DERUTCTime( + byte[] bytes) + { + // + // explicitly convert to characters + // + char[] dateC = new char[bytes.length]; + + for (int i = 0; i != dateC.length; i++) + { + dateC[i] = (char)(bytes[i] & 0xff); + } + + this.time = new String(dateC); + } + + /** + * return the time as a date based on whatever a 2 digit year will return. For + * standardised processing use getAdjustedDate(). + * + * @return the resulting date + * @exception ParseException if the date string cannot be parsed. + */ + public Date getDate() + throws ParseException + { + SimpleDateFormat dateF = new SimpleDateFormat("yyMMddHHmmssz"); + + return dateF.parse(getTime()); + } + + /** + * return the time as an adjusted date + * in the range of 1950 - 2049. + * + * @return a date in the range of 1950 to 2049. + * @exception ParseException if the date string cannot be parsed. + */ + public Date getAdjustedDate() + throws ParseException + { + SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmssz"); + + dateF.setTimeZone(new SimpleTimeZone(0, "Z")); + + return dateF.parse(getAdjustedTime()); + } + + /** + * return the time - always in the form of + * YYMMDDhhmmssGMT(+hh:mm|-hh:mm). + *

+ * Normally in a certificate we would expect "Z" rather than "GMT", + * however adding the "GMT" means we can just use: + *

+     *     dateF = new SimpleDateFormat("yyMMddHHmmssz");
+     * 
+ * To read in the time and get a date which is compatible with our local + * time zone. + *

+ * Note: In some cases, due to the local date processing, this + * may lead to unexpected results. If you want to stick the normal + * convention of 1950 to 2049 use the getAdjustedTime() method. + */ + public String getTime() + { + // + // standardise the format. + // + if (time.indexOf('-') < 0 && time.indexOf('+') < 0) + { + if (time.length() == 11) + { + return time.substring(0, 10) + "00GMT+00:00"; + } + else + { + return time.substring(0, 12) + "GMT+00:00"; + } + } + else + { + int index = time.indexOf('-'); + if (index < 0) + { + index = time.indexOf('+'); + } + String d = time; + + if (index == time.length() - 3) + { + d += "00"; + } + + if (index == 10) + { + return d.substring(0, 10) + "00GMT" + d.substring(10, 13) + ":" + d.substring(13, 15); + } + else + { + return d.substring(0, 12) + "GMT" + d.substring(12, 15) + ":" + d.substring(15, 17); + } + } + } + + /** + * return a time string as an adjusted date with a 4 digit year. This goes + * in the range of 1950 - 2049. + */ + public String getAdjustedTime() + { + String d = this.getTime(); + + if (d.charAt(0) < '5') + { + return "20" + d; + } + else + { + return "19" + d; + } + } + + private byte[] getOctets() + { + char[] cs = time.toCharArray(); + byte[] bs = new byte[cs.length]; + + for (int i = 0; i != cs.length; i++) + { + bs[i] = (byte)cs[i]; + } + + return bs; + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(UTC_TIME, this.getOctets()); + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof DERUTCTime)) + { + return false; + } + + return time.equals(((DERUTCTime)o).time); + } + + public int hashCode() + { + return time.hashCode(); + } + + public String toString() + { + return time; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERUTF8String.java b/src/com/google/bitcoin/bouncycastle/asn1/DERUTF8String.java new file mode 100644 index 000000000..d36521b84 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERUTF8String.java @@ -0,0 +1,109 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import com.google.bitcoin.bouncycastle.util.Strings; + +import java.io.IOException; + +/** + * DER UTF8String object. + */ +public class DERUTF8String + extends ASN1Object + implements DERString +{ + String string; + + /** + * return an UTF8 string from the passed in object. + * + * @exception IllegalArgumentException + * if the object cannot be converted. + */ + public static DERUTF8String getInstance(Object obj) + { + if (obj == null || obj instanceof DERUTF8String) + { + return (DERUTF8String)obj; + } + + if (obj instanceof ASN1OctetString) + { + return new DERUTF8String(((ASN1OctetString)obj).getOctets()); + } + + if (obj instanceof ASN1TaggedObject) + { + return getInstance(((ASN1TaggedObject)obj).getObject()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + + obj.getClass().getName()); + } + + /** + * return an UTF8 String from a tagged object. + * + * @param obj + * the tagged object holding the object we want + * @param explicit + * true if the object is meant to be explicitly tagged false + * otherwise. + * @exception IllegalArgumentException + * if the tagged object cannot be converted. + */ + public static DERUTF8String getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + /** + * basic constructor - byte encoded string. + */ + DERUTF8String(byte[] string) + { + this.string = Strings.fromUTF8ByteArray(string); + } + + /** + * basic constructor + */ + public DERUTF8String(String string) + { + this.string = string; + } + + public String getString() + { + return string; + } + + public String toString() + { + return string; + } + + public int hashCode() + { + return this.getString().hashCode(); + } + + boolean asn1Equals(DERObject o) + { + if (!(o instanceof DERUTF8String)) + { + return false; + } + + DERUTF8String s = (DERUTF8String)o; + + return this.getString().equals(s.getString()); + } + + void encode(DEROutputStream out) + throws IOException + { + out.writeEncoded(UTF8_STRING, Strings.toUTF8ByteArray(string)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERUniversalString.java b/src/com/google/bitcoin/bouncycastle/asn1/DERUniversalString.java new file mode 100644 index 000000000..01e346e7b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERUniversalString.java @@ -0,0 +1,120 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * DER UniversalString object. + */ +public class DERUniversalString + extends ASN1Object + implements DERString +{ + private static final char[] table = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + private byte[] string; + + /** + * return a Universal String from the passed in object. + * + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static DERUniversalString getInstance( + Object obj) + { + if (obj == null || obj instanceof DERUniversalString) + { + return (DERUniversalString)obj; + } + + if (obj instanceof ASN1OctetString) + { + return new DERUniversalString(((ASN1OctetString)obj).getOctets()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * return a Universal String from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static DERUniversalString getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + /** + * basic constructor - byte encoded string. + */ + public DERUniversalString( + byte[] string) + { + this.string = string; + } + + public String getString() + { + StringBuffer buf = new StringBuffer("#"); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ASN1OutputStream aOut = new ASN1OutputStream(bOut); + + try + { + aOut.writeObject(this); + } + catch (IOException e) + { + throw new RuntimeException("internal error encoding BitString"); + } + + byte[] string = bOut.toByteArray(); + + for (int i = 0; i != string.length; i++) + { + buf.append(table[(string[i] >>> 4) & 0xf]); + buf.append(table[string[i] & 0xf]); + } + + return buf.toString(); + } + + public String toString() + { + return getString(); + } + + public byte[] getOctets() + { + return string; + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(UNIVERSAL_STRING, this.getOctets()); + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof DERUniversalString)) + { + return false; + } + + return this.getString().equals(((DERUniversalString)o).getString()); + } + + public int hashCode() + { + return this.getString().hashCode(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERUnknownTag.java b/src/com/google/bitcoin/bouncycastle/asn1/DERUnknownTag.java new file mode 100644 index 000000000..53b52e25a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERUnknownTag.java @@ -0,0 +1,79 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +import com.google.bitcoin.bouncycastle.util.Arrays; + +/** + * We insert one of these when we find a tag we don't recognise. + */ +public class DERUnknownTag + extends DERObject +{ + private boolean isConstructed; + private int tag; + private byte[] data; + + /** + * @param tag the tag value. + * @param data the contents octets. + */ + public DERUnknownTag( + int tag, + byte[] data) + { + this(false, tag, data); + } + + public DERUnknownTag( + boolean isConstructed, + int tag, + byte[] data) + { + this.isConstructed = isConstructed; + this.tag = tag; + this.data = data; + } + + public boolean isConstructed() + { + return isConstructed; + } + + public int getTag() + { + return tag; + } + + public byte[] getData() + { + return data; + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(isConstructed ? DERTags.CONSTRUCTED : 0, tag, data); + } + + public boolean equals( + Object o) + { + if (!(o instanceof DERUnknownTag)) + { + return false; + } + + DERUnknownTag other = (DERUnknownTag)o; + + return isConstructed == other.isConstructed + && tag == other.tag + && Arrays.areEqual(data, other.data); + } + + public int hashCode() + { + return (isConstructed ? ~0 : 0) ^ tag ^ Arrays.hashCode(data); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DERVisibleString.java b/src/com/google/bitcoin/bouncycastle/asn1/DERVisibleString.java new file mode 100644 index 000000000..f7675a300 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DERVisibleString.java @@ -0,0 +1,126 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; + +/** + * DER VisibleString object. + */ +public class DERVisibleString + extends ASN1Object + implements DERString +{ + String string; + + /** + * return a Visible String from the passed in object. + * + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static DERVisibleString getInstance( + Object obj) + { + if (obj == null || obj instanceof DERVisibleString) + { + return (DERVisibleString)obj; + } + + if (obj instanceof ASN1OctetString) + { + return new DERVisibleString(((ASN1OctetString)obj).getOctets()); + } + + if (obj instanceof ASN1TaggedObject) + { + return getInstance(((ASN1TaggedObject)obj).getObject()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * return a Visible String from a tagged object. + * + * @param obj the tagged object holding the object we want + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the tagged object cannot + * be converted. + */ + public static DERVisibleString getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + /** + * basic constructor - byte encoded string. + */ + public DERVisibleString( + byte[] string) + { + char[] cs = new char[string.length]; + + for (int i = 0; i != cs.length; i++) + { + cs[i] = (char)(string[i] & 0xff); + } + + this.string = new String(cs); + } + + /** + * basic constructor + */ + public DERVisibleString( + String string) + { + this.string = string; + } + + public String getString() + { + return string; + } + + public String toString() + { + return string; + } + + public byte[] getOctets() + { + char[] cs = string.toCharArray(); + byte[] bs = new byte[cs.length]; + + for (int i = 0; i != cs.length; i++) + { + bs[i] = (byte)cs[i]; + } + + return bs; + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(VISIBLE_STRING, this.getOctets()); + } + + boolean asn1Equals( + DERObject o) + { + if (!(o instanceof DERVisibleString)) + { + return false; + } + + return this.getString().equals(((DERVisibleString)o).getString()); + } + + public int hashCode() + { + return this.getString().hashCode(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/DefiniteLengthInputStream.java b/src/com/google/bitcoin/bouncycastle/asn1/DefiniteLengthInputStream.java new file mode 100644 index 000000000..55c7cb53c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/DefiniteLengthInputStream.java @@ -0,0 +1,105 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +import com.google.bitcoin.bouncycastle.util.io.Streams; + +class DefiniteLengthInputStream + extends LimitedInputStream +{ + private static final byte[] EMPTY_BYTES = new byte[0]; + + private final int _originalLength; + private int _remaining; + + DefiniteLengthInputStream( + InputStream in, + int length) + { + super(in); + + if (length < 0) + { + throw new IllegalArgumentException("negative lengths not allowed"); + } + + this._originalLength = length; + this._remaining = length; + + if (length == 0) + { + setParentEofDetect(true); + } + } + + int getRemaining() + { + return _remaining; + } + + public int read() + throws IOException + { + if (_remaining == 0) + { + return -1; + } + + int b = _in.read(); + + if (b < 0) + { + throw new EOFException("DEF length " + _originalLength + " object truncated by " + _remaining); + } + + if (--_remaining == 0) + { + setParentEofDetect(true); + } + + return b; + } + + public int read(byte[] buf, int off, int len) + throws IOException + { + if (_remaining == 0) + { + return -1; + } + + int toRead = Math.min(len, _remaining); + int numRead = _in.read(buf, off, toRead); + + if (numRead < 0) + { + throw new EOFException("DEF length " + _originalLength + " object truncated by " + _remaining); + } + + if ((_remaining -= numRead) == 0) + { + setParentEofDetect(true); + } + + return numRead; + } + + byte[] toByteArray() + throws IOException + { + if (_remaining == 0) + { + return EMPTY_BYTES; + } + + byte[] bytes = new byte[_remaining]; + if ((_remaining -= Streams.readFully(_in, bytes)) != 0) + { + throw new EOFException("DEF length " + _originalLength + " object truncated by " + _remaining); + } + setParentEofDetect(true); + return bytes; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/IndefiniteLengthInputStream.java b/src/com/google/bitcoin/bouncycastle/asn1/IndefiniteLengthInputStream.java new file mode 100644 index 000000000..cf9e17430 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/IndefiniteLengthInputStream.java @@ -0,0 +1,110 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +class IndefiniteLengthInputStream + extends LimitedInputStream +{ + private int _b1; + private int _b2; + private boolean _eofReached = false; + private boolean _eofOn00 = true; + + IndefiniteLengthInputStream( + InputStream in) + throws IOException + { + super(in); + + _b1 = in.read(); + _b2 = in.read(); + + if (_b2 < 0) + { + // Corrupted stream + throw new EOFException(); + } + + checkForEof(); + } + + void setEofOn00( + boolean eofOn00) + { + _eofOn00 = eofOn00; + checkForEof(); + } + + private boolean checkForEof() + { + if (!_eofReached && _eofOn00 && (_b1 == 0x00 && _b2 == 0x00)) + { + _eofReached = true; + setParentEofDetect(true); + } + return _eofReached; + } + + public int read(byte[] b, int off, int len) + throws IOException + { + // Only use this optimisation if we aren't checking for 00 + if (_eofOn00 || len < 3) + { + return super.read(b, off, len); + } + + if (_eofReached) + { + return -1; + } + + int numRead = _in.read(b, off + 2, len - 2); + + if (numRead < 0) + { + // Corrupted stream + throw new EOFException(); + } + + b[off] = (byte)_b1; + b[off + 1] = (byte)_b2; + + _b1 = _in.read(); + _b2 = _in.read(); + + if (_b2 < 0) + { + // Corrupted stream + throw new EOFException(); + } + + return numRead + 2; + } + + public int read() + throws IOException + { + if (checkForEof()) + { + return -1; + } + + int b = _in.read(); + + if (b < 0) + { + // Corrupted stream + throw new EOFException(); + } + + int v = _b1; + + _b1 = _b2; + _b2 = b; + + return v; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/LazyDERConstructionEnumeration.java b/src/com/google/bitcoin/bouncycastle/asn1/LazyDERConstructionEnumeration.java new file mode 100644 index 000000000..c5d5233c6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/LazyDERConstructionEnumeration.java @@ -0,0 +1,43 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.util.Enumeration; +import java.io.IOException; + +class LazyDERConstructionEnumeration + implements Enumeration +{ + private ASN1InputStream aIn; + private Object nextObj; + + public LazyDERConstructionEnumeration(byte[] encoded) + { + aIn = new ASN1InputStream(encoded, true); + nextObj = readObject(); + } + + public boolean hasMoreElements() + { + return nextObj != null; + } + + public Object nextElement() + { + Object o = nextObj; + + nextObj = readObject(); + + return o; + } + + private Object readObject() + { + try + { + return aIn.readObject(); + } + catch (IOException e) + { + throw new ASN1ParsingException("malformed DER construction: " + e, e); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/LazyDERSequence.java b/src/com/google/bitcoin/bouncycastle/asn1/LazyDERSequence.java new file mode 100644 index 000000000..a5ce2a894 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/LazyDERSequence.java @@ -0,0 +1,75 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.IOException; +import java.util.Enumeration; + +public class LazyDERSequence + extends DERSequence +{ + private byte[] encoded; + private boolean parsed = false; + private int size = -1; + + LazyDERSequence( + byte[] encoded) + throws IOException + { + this.encoded = encoded; + } + + private void parse() + { + Enumeration en = new LazyDERConstructionEnumeration(encoded); + + while (en.hasMoreElements()) + { + addObject((DEREncodable)en.nextElement()); + } + + parsed = true; + } + + public DEREncodable getObjectAt(int index) + { + if (!parsed) + { + parse(); + } + + return super.getObjectAt(index); + } + + public Enumeration getObjects() + { + if (parsed) + { + return super.getObjects(); + } + + return new LazyDERConstructionEnumeration(encoded); + } + + public int size() + { + if (size < 0) + { + Enumeration en = new LazyDERConstructionEnumeration(encoded); + + size = 0; + while (en.hasMoreElements()) + { + en.nextElement(); + size++; + } + } + + return size; + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(SEQUENCE | CONSTRUCTED, encoded); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/LimitedInputStream.java b/src/com/google/bitcoin/bouncycastle/asn1/LimitedInputStream.java new file mode 100644 index 000000000..233438f40 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/LimitedInputStream.java @@ -0,0 +1,23 @@ +package com.google.bitcoin.bouncycastle.asn1; + +import java.io.InputStream; + +abstract class LimitedInputStream + extends InputStream +{ + protected final InputStream _in; + + LimitedInputStream( + InputStream in) + { + this._in = in; + } + + protected void setParentEofDetect(boolean on) + { + if (_in instanceof IndefiniteLengthInputStream) + { + ((IndefiniteLengthInputStream)_in).setEofOn00(on); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/OIDTokenizer.java b/src/com/google/bitcoin/bouncycastle/asn1/OIDTokenizer.java new file mode 100644 index 000000000..461265ae3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/OIDTokenizer.java @@ -0,0 +1,48 @@ +package com.google.bitcoin.bouncycastle.asn1; + +/** + * class for breaking up an OID into it's component tokens, ala + * java.util.StringTokenizer. We need this class as some of the + * lightweight Java environment don't support classes like + * StringTokenizer. + */ +public class OIDTokenizer +{ + private String oid; + private int index; + + public OIDTokenizer( + String oid) + { + this.oid = oid; + this.index = 0; + } + + public boolean hasMoreTokens() + { + return (index != -1); + } + + public String nextToken() + { + if (index == -1) + { + return null; + } + + String token; + int end = oid.indexOf('.', index); + + if (end == -1) + { + token = oid.substring(index); + index = -1; + return token; + } + + token = oid.substring(index, end); + + index = end + 1; + return token; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/bc/BCObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/bc/BCObjectIdentifiers.java new file mode 100644 index 000000000..8d0e82523 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/bc/BCObjectIdentifiers.java @@ -0,0 +1,51 @@ +package com.google.bitcoin.bouncycastle.asn1.bc; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface BCObjectIdentifiers +{ + /** + * iso.org.dod.internet.private.enterprise.legion-of-the-bouncy-castle + * + * 1.3.6.1.4.1.22554 + */ + public static final DERObjectIdentifier bc = new DERObjectIdentifier("1.3.6.1.4.1.22554"); + + /** + * pbe(1) algorithms + */ + public static final DERObjectIdentifier bc_pbe = new DERObjectIdentifier(bc.getId() + ".1"); + + /** + * SHA-1(1) + */ + public static final DERObjectIdentifier bc_pbe_sha1 = new DERObjectIdentifier(bc_pbe.getId() + ".1"); + + /** + * SHA-2(2) . (SHA-256(1)|SHA-384(2)|SHA-512(3)|SHA-224(4)) + */ + public static final DERObjectIdentifier bc_pbe_sha256 = new DERObjectIdentifier(bc_pbe.getId() + ".2.1"); + public static final DERObjectIdentifier bc_pbe_sha384 = new DERObjectIdentifier(bc_pbe.getId() + ".2.2"); + public static final DERObjectIdentifier bc_pbe_sha512 = new DERObjectIdentifier(bc_pbe.getId() + ".2.3"); + public static final DERObjectIdentifier bc_pbe_sha224 = new DERObjectIdentifier(bc_pbe.getId() + ".2.4"); + + /** + * PKCS-5(1)|PKCS-12(2) + */ + public static final DERObjectIdentifier bc_pbe_sha1_pkcs5 = new DERObjectIdentifier(bc_pbe_sha1.getId() + ".1"); + public static final DERObjectIdentifier bc_pbe_sha1_pkcs12 = new DERObjectIdentifier(bc_pbe_sha1.getId() + ".2"); + + public static final DERObjectIdentifier bc_pbe_sha256_pkcs5 = new DERObjectIdentifier(bc_pbe_sha256.getId() + ".1"); + public static final DERObjectIdentifier bc_pbe_sha256_pkcs12 = new DERObjectIdentifier(bc_pbe_sha256.getId() + ".2"); + + /** + * AES(1) . (CBC-128(2)|CBC-192(22)|CBC-256(42)) + */ + public static final DERObjectIdentifier bc_pbe_sha1_pkcs12_aes128_cbc = new DERObjectIdentifier(bc_pbe_sha1_pkcs12.getId() + ".1.2"); + public static final DERObjectIdentifier bc_pbe_sha1_pkcs12_aes192_cbc = new DERObjectIdentifier(bc_pbe_sha1_pkcs12.getId() + ".1.22"); + public static final DERObjectIdentifier bc_pbe_sha1_pkcs12_aes256_cbc = new DERObjectIdentifier(bc_pbe_sha1_pkcs12.getId() + ".1.42"); + + public static final DERObjectIdentifier bc_pbe_sha256_pkcs12_aes128_cbc = new DERObjectIdentifier(bc_pbe_sha256_pkcs12.getId() + ".1.2"); + public static final DERObjectIdentifier bc_pbe_sha256_pkcs12_aes192_cbc = new DERObjectIdentifier(bc_pbe_sha256_pkcs12.getId() + ".1.22"); + public static final DERObjectIdentifier bc_pbe_sha256_pkcs12_aes256_cbc = new DERObjectIdentifier(bc_pbe_sha256_pkcs12.getId() + ".1.42"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/CAKeyUpdAnnContent.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CAKeyUpdAnnContent.java new file mode 100644 index 000000000..e98aa102a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CAKeyUpdAnnContent.java @@ -0,0 +1,73 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class CAKeyUpdAnnContent + extends ASN1Encodable +{ + private CMPCertificate oldWithNew; + private CMPCertificate newWithOld; + private CMPCertificate newWithNew; + + private CAKeyUpdAnnContent(ASN1Sequence seq) + { + oldWithNew = CMPCertificate.getInstance(seq.getObjectAt(0)); + newWithOld = CMPCertificate.getInstance(seq.getObjectAt(1)); + newWithNew = CMPCertificate.getInstance(seq.getObjectAt(2)); + } + + public static CAKeyUpdAnnContent getInstance(Object o) + { + if (o instanceof CAKeyUpdAnnContent) + { + return (CAKeyUpdAnnContent)o; + } + + if (o instanceof ASN1Sequence) + { + return new CAKeyUpdAnnContent((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public CMPCertificate getOldWithNew() + { + return oldWithNew; + } + + public CMPCertificate getNewWithOld() + { + return newWithOld; + } + + public CMPCertificate getNewWithNew() + { + return newWithNew; + } + + /** + *

+     * CAKeyUpdAnnContent ::= SEQUENCE {
+     *                             oldWithNew   CMPCertificate, -- old pub signed with new priv
+     *                             newWithOld   CMPCertificate, -- new pub signed with old priv
+     *                             newWithNew   CMPCertificate  -- new pub signed with new priv
+     *  }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(oldWithNew); + v.add(newWithOld); + v.add(newWithNew); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/CMPCertificate.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CMPCertificate.java new file mode 100644 index 000000000..dbc585d3b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CMPCertificate.java @@ -0,0 +1,62 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.x509.X509CertificateStructure; + +public class CMPCertificate + extends ASN1Encodable + implements ASN1Choice +{ + private X509CertificateStructure x509v3PKCert; + + public CMPCertificate(X509CertificateStructure x509v3PKCert) + { + if (x509v3PKCert.getVersion() != 3) + { + throw new IllegalArgumentException("only version 3 certificates allowed"); + } + + this.x509v3PKCert = x509v3PKCert; + } + + public static CMPCertificate getInstance(Object o) + { + if (o instanceof CMPCertificate) + { + return (CMPCertificate)o; + } + + if (o instanceof X509CertificateStructure) + { + return new CMPCertificate((X509CertificateStructure)o); + } + + if (o instanceof ASN1Sequence) + { + return new CMPCertificate(X509CertificateStructure.getInstance(o)); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public X509CertificateStructure getX509v3PKCert() + { + return x509v3PKCert; + } + + /** + *
+     * CMPCertificate ::= CHOICE {
+     *            x509v3PKCert        Certificate
+     *  }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + return x509v3PKCert.toASN1Object(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/CRLAnnContent.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CRLAnnContent.java new file mode 100644 index 000000000..4b8c29644 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CRLAnnContent.java @@ -0,0 +1,55 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.x509.CertificateList; + +public class CRLAnnContent + extends ASN1Encodable +{ + private ASN1Sequence content; + + private CRLAnnContent(ASN1Sequence seq) + { + content = seq; + } + + public static CRLAnnContent getInstance(Object o) + { + if (o instanceof CRLAnnContent) + { + return (CRLAnnContent)o; + } + + if (o instanceof ASN1Sequence) + { + return new CRLAnnContent((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public CertificateList[] toCertificateListArray() + { + CertificateList[] result = new CertificateList[content.size()]; + + for (int i = 0; i != result.length; i++) + { + result[i] = CertificateList.getInstance(content.getObjectAt(i)); + } + + return result; + } + + /** + *
+     * CRLAnnContent ::= SEQUENCE OF CertificateList
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + return content; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertConfirmContent.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertConfirmContent.java new file mode 100644 index 000000000..34da50ee6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertConfirmContent.java @@ -0,0 +1,54 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class CertConfirmContent + extends ASN1Encodable +{ + private ASN1Sequence content; + + private CertConfirmContent(ASN1Sequence seq) + { + content = seq; + } + + public static CertConfirmContent getInstance(Object o) + { + if (o instanceof CertConfirmContent) + { + return (CertConfirmContent)o; + } + + if (o instanceof ASN1Sequence) + { + return new CertConfirmContent((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public CertStatus[] toCertStatusArray() + { + CertStatus[] result = new CertStatus[content.size()]; + + for (int i = 0; i != result.length; i++) + { + result[i] = CertStatus.getInstance(content.getObjectAt(i)); + } + + return result; + } + + /** + *
+     * CertConfirmContent ::= SEQUENCE OF CertStatus
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + return content; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertOrEncCert.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertOrEncCert.java new file mode 100644 index 000000000..0e885669b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertOrEncCert.java @@ -0,0 +1,76 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.crmf.EncryptedValue; + +public class CertOrEncCert + extends ASN1Encodable + implements ASN1Choice +{ + private CMPCertificate certificate; + private EncryptedValue encryptedCert; + + private CertOrEncCert(ASN1TaggedObject tagged) + { + if (tagged.getTagNo() == 0) + { + certificate = CMPCertificate.getInstance(tagged.getObject()); + } + else if (tagged.getTagNo() == 1) + { + encryptedCert = EncryptedValue.getInstance(tagged.getObject()); + } + else + { + throw new IllegalArgumentException("unknown tag: " + tagged.getTagNo()); + } + } + + public static CertOrEncCert getInstance(Object o) + { + if (o instanceof CertOrEncCert) + { + return (CertOrEncCert)o; + } + + if (o instanceof ASN1TaggedObject) + { + return new CertOrEncCert((ASN1TaggedObject)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public CMPCertificate getCertificate() + { + return certificate; + } + + public EncryptedValue getEncryptedCert() + { + return encryptedCert; + } + + /** + *
+     * CertOrEncCert ::= CHOICE {
+     *                      certificate     [0] CMPCertificate,
+     *                      encryptedCert   [1] EncryptedValue
+     *           }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + if (certificate != null) + { + return new DERTaggedObject(true, 0, certificate); + } + + return new DERTaggedObject(true, 1, encryptedCert); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertRepMessage.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertRepMessage.java new file mode 100644 index 000000000..608e9aa78 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertRepMessage.java @@ -0,0 +1,96 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class CertRepMessage + extends ASN1Encodable +{ + private ASN1Sequence caPubs; + private ASN1Sequence response; + + private CertRepMessage(ASN1Sequence seq) + { + int index = 0; + + if (seq.size() > 1) + { + caPubs = ASN1Sequence.getInstance((ASN1TaggedObject)seq.getObjectAt(index++), true); + } + + response = ASN1Sequence.getInstance(seq.getObjectAt(index)); + } + + public static CertRepMessage getInstance(Object o) + { + if (o instanceof CertRepMessage) + { + return (CertRepMessage)o; + } + + if (o instanceof ASN1Sequence) + { + return new CertRepMessage((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public CMPCertificate[] getCaPubs() + { + if (caPubs == null) + { + return null; + } + + CMPCertificate[] results = new CMPCertificate[caPubs.size()]; + + for (int i = 0; i != results.length; i++) + { + results[i] = CMPCertificate.getInstance(caPubs.getObjectAt(i)); + } + + return results; + } + + public CertResponse[] getResponse() + { + CertResponse[] results = new CertResponse[response.size()]; + + for (int i = 0; i != results.length; i++) + { + results[i] = CertResponse.getInstance(response.getObjectAt(i)); + } + + return results; + } + + /** + *
+     * CertRepMessage ::= SEQUENCE {
+     *                          caPubs       [1] SEQUENCE SIZE (1..MAX) OF CMPCertificate
+     *                                                                             OPTIONAL,
+     *                          response         SEQUENCE OF CertResponse
+     * }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (caPubs != null) + { + v.add(new DERTaggedObject(true, 1, caPubs)); + } + + v.add(response); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertResponse.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertResponse.java new file mode 100644 index 000000000..0cd691a08 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertResponse.java @@ -0,0 +1,112 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class CertResponse + extends ASN1Encodable +{ + private DERInteger certReqId; + private PKIStatusInfo status; + private CertifiedKeyPair certifiedKeyPair; + private ASN1OctetString rspInfo; + + private CertResponse(ASN1Sequence seq) + { + certReqId = DERInteger.getInstance(seq.getObjectAt(0)); + status = PKIStatusInfo.getInstance(seq.getObjectAt(1)); + + if (seq.size() >= 3) + { + if (seq.size() == 3) + { + DEREncodable o = seq.getObjectAt(2); + if (o instanceof ASN1OctetString) + { + rspInfo = ASN1OctetString.getInstance(o); + } + else + { + certifiedKeyPair = CertifiedKeyPair.getInstance(o); + } + } + else + { + certifiedKeyPair = CertifiedKeyPair.getInstance(seq.getObjectAt(2)); + rspInfo = ASN1OctetString.getInstance(seq.getObjectAt(3)); + } + } + } + + public static CertResponse getInstance(Object o) + { + if (o instanceof CertResponse) + { + return (CertResponse)o; + } + + if (o instanceof ASN1Sequence) + { + return new CertResponse((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public DERInteger getCertReqId() + { + return certReqId; + } + + public PKIStatusInfo getStatus() + { + return status; + } + + public CertifiedKeyPair getCertifiedKeyPair() + { + return certifiedKeyPair; + } + + /** + *
+     * CertResponse ::= SEQUENCE {
+     *                            certReqId           INTEGER,
+     *                            -- to match this response with corresponding request (a value
+     *                            -- of -1 is to be used if certReqId is not specified in the
+     *                            -- corresponding request)
+     *                            status              PKIStatusInfo,
+     *                            certifiedKeyPair    CertifiedKeyPair    OPTIONAL,
+     *                            rspInfo             OCTET STRING        OPTIONAL
+     *                            -- analogous to the id-regInfo-utf8Pairs string defined
+     *                            -- for regInfo in CertReqMsg [CRMF]
+     *             }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(certReqId); + v.add(status); + + if (certifiedKeyPair != null) + { + v.add(certifiedKeyPair); + } + + if (rspInfo != null) + { + v.add(rspInfo); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertStatus.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertStatus.java new file mode 100644 index 000000000..ff767a1f5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertStatus.java @@ -0,0 +1,81 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class CertStatus + extends ASN1Encodable +{ + private ASN1OctetString certHash; + private DERInteger certReqId; + private PKIStatusInfo statusInfo; + + private CertStatus(ASN1Sequence seq) + { + certHash = ASN1OctetString.getInstance(seq.getObjectAt(0)); + certReqId = DERInteger.getInstance(seq.getObjectAt(1)); + + if (seq.size() > 2) + { + statusInfo = PKIStatusInfo.getInstance(seq.getObjectAt(2)); + } + } + + public static CertStatus getInstance(Object o) + { + if (o instanceof CertStatus) + { + return (CertStatus)o; + } + + if (o instanceof ASN1Sequence) + { + return new CertStatus((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public DERInteger getCertReqId() + { + return certReqId; + } + + public PKIStatusInfo getStatusInfo() + { + return statusInfo; + } + + /** + *
+     * CertStatus ::= SEQUENCE {
+     *                   certHash    OCTET STRING,
+     *                   -- the hash of the certificate, using the same hash algorithm
+     *                   -- as is used to create and verify the certificate signature
+     *                   certReqId   INTEGER,
+     *                   -- to match this confirmation with the corresponding req/rep
+     *                   statusInfo  PKIStatusInfo OPTIONAL
+     * }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(certHash); + v.add(certReqId); + + if (statusInfo != null) + { + v.add(statusInfo); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertifiedKeyPair.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertifiedKeyPair.java new file mode 100644 index 000000000..ab2621c1f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/CertifiedKeyPair.java @@ -0,0 +1,105 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.crmf.EncryptedValue; +import com.google.bitcoin.bouncycastle.asn1.crmf.PKIPublicationInfo; + +public class CertifiedKeyPair + extends ASN1Encodable +{ + private CertOrEncCert certOrEncCert; + private EncryptedValue privateKey; + private PKIPublicationInfo publicationInfo; + + private CertifiedKeyPair(ASN1Sequence seq) + { + certOrEncCert = CertOrEncCert.getInstance(seq.getObjectAt(0)); + + if (seq.size() >= 2) + { + if (seq.size() == 2) + { + ASN1TaggedObject tagged = ASN1TaggedObject.getInstance(seq.getObjectAt(1)); + if (tagged.getTagNo() == 0) + { + privateKey = EncryptedValue.getInstance(tagged.getObject()); + } + else + { + publicationInfo = PKIPublicationInfo.getInstance(tagged.getObject()); + } + } + else + { + privateKey = EncryptedValue.getInstance(ASN1TaggedObject.getInstance(seq.getObjectAt(1))); + publicationInfo = PKIPublicationInfo.getInstance(ASN1TaggedObject.getInstance(seq.getObjectAt(2))); + } + } + } + + public static CertifiedKeyPair getInstance(Object o) + { + if (o instanceof CertifiedKeyPair) + { + return (CertifiedKeyPair)o; + } + + if (o instanceof ASN1Sequence) + { + return new CertifiedKeyPair((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public CertOrEncCert getCertOrEncCert() + { + return certOrEncCert; + } + + public EncryptedValue getPrivateKey() + { + return privateKey; + } + + public PKIPublicationInfo getPublicationInfo() + { + return publicationInfo; + } + + /** + *
+     * CertifiedKeyPair ::= SEQUENCE {
+     *                                  certOrEncCert       CertOrEncCert,
+     *                                  privateKey      [0] EncryptedValue      OPTIONAL,
+     *                                  -- see [CRMF] for comment on encoding
+     *                                  publicationInfo [1] PKIPublicationInfo  OPTIONAL
+     *       }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(certOrEncCert); + + if (privateKey != null) + { + v.add(new DERTaggedObject(true, 0, privateKey)); + } + + if (publicationInfo != null) + { + v.add(new DERTaggedObject(true, 1, publicationInfo)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/Challenge.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/Challenge.java new file mode 100644 index 000000000..fe853234f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/Challenge.java @@ -0,0 +1,96 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class Challenge + extends ASN1Encodable +{ + private AlgorithmIdentifier owf; + private ASN1OctetString witness; + private ASN1OctetString challenge; + + private Challenge(ASN1Sequence seq) + { + int index = 0; + + if (seq.size() == 3) + { + owf = AlgorithmIdentifier.getInstance(seq.getObjectAt(index++)); + } + + witness = ASN1OctetString.getInstance(seq.getObjectAt(index++)); + challenge = ASN1OctetString.getInstance(seq.getObjectAt(index)); + } + + public static Challenge getInstance(Object o) + { + if (o instanceof Challenge) + { + return (Challenge)o; + } + + if (o instanceof ASN1Sequence) + { + return new Challenge((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public AlgorithmIdentifier getOwf() + { + return owf; + } + + /** + *
+     * Challenge ::= SEQUENCE {
+     *                 owf                 AlgorithmIdentifier  OPTIONAL,
+     *
+     *                 -- MUST be present in the first Challenge; MAY be omitted in
+     *                 -- any subsequent Challenge in POPODecKeyChallContent (if
+     *                 -- omitted, then the owf used in the immediately preceding
+     *                 -- Challenge is to be used).
+     *
+     *                 witness             OCTET STRING,
+     *                 -- the result of applying the one-way function (owf) to a
+     *                 -- randomly-generated INTEGER, A.  [Note that a different
+     *                 -- INTEGER MUST be used for each Challenge.]
+     *                 challenge           OCTET STRING
+     *                 -- the encryption (under the public key for which the cert.
+     *                 -- request is being made) of Rand, where Rand is specified as
+     *                 --   Rand ::= SEQUENCE {
+     *                 --      int      INTEGER,
+     *                 --       - the randomly-generated INTEGER A (above)
+     *                 --      sender   GeneralName
+     *                 --       - the sender's name (as included in PKIHeader)
+     *                 --   }
+     *      }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + addOptional(v, owf); + v.add(witness); + v.add(challenge); + + return new DERSequence(v); + } + + private void addOptional(ASN1EncodableVector v, ASN1Encodable obj) + { + if (obj != null) + { + v.add(obj); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/ErrorMsgContent.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/ErrorMsgContent.java new file mode 100644 index 000000000..c55018d10 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/ErrorMsgContent.java @@ -0,0 +1,100 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +import java.util.Enumeration; + +public class ErrorMsgContent + extends ASN1Encodable +{ + private PKIStatusInfo pKIStatusInfo; + private DERInteger errorCode; + private PKIFreeText errorDetails; + + private ErrorMsgContent(ASN1Sequence seq) + { + Enumeration en = seq.getObjects(); + + pKIStatusInfo = PKIStatusInfo.getInstance(en.nextElement()); + + while (en.hasMoreElements()) + { + Object o = en.nextElement(); + + if (o instanceof DERInteger) + { + errorCode = DERInteger.getInstance(o); + } + else + { + errorDetails = PKIFreeText.getInstance(o); + } + } + } + + public static ErrorMsgContent getInstance(Object o) + { + if (o instanceof ErrorMsgContent) + { + return (ErrorMsgContent)o; + } + + if (o instanceof ASN1Sequence) + { + return new ErrorMsgContent((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public PKIStatusInfo getPKIStatusInfo() + { + return pKIStatusInfo; + } + + public DERInteger getErrorCode() + { + return errorCode; + } + + public PKIFreeText getErrorDetails() + { + return errorDetails; + } + + /** + *
+     * ErrorMsgContent ::= SEQUENCE {
+     *                        pKIStatusInfo          PKIStatusInfo,
+     *                        errorCode              INTEGER           OPTIONAL,
+     *                        -- implementation-specific error codes
+     *                        errorDetails           PKIFreeText       OPTIONAL
+     *                        -- implementation-specific error details
+     * }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(pKIStatusInfo); + addOptional(v, errorCode); + addOptional(v, errorDetails); + + return new DERSequence(v); + } + + private void addOptional(ASN1EncodableVector v, ASN1Encodable obj) + { + if (obj != null) + { + v.add(obj); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/GenMsgContent.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/GenMsgContent.java new file mode 100644 index 000000000..9355bf927 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/GenMsgContent.java @@ -0,0 +1,54 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class GenMsgContent + extends ASN1Encodable +{ + private ASN1Sequence content; + + private GenMsgContent(ASN1Sequence seq) + { + content = seq; + } + + public static GenMsgContent getInstance(Object o) + { + if (o instanceof GenMsgContent) + { + return (GenMsgContent)o; + } + + if (o instanceof ASN1Sequence) + { + return new GenMsgContent((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public InfoTypeAndValue[] toInfoTypeAndValueArray() + { + InfoTypeAndValue[] result = new InfoTypeAndValue[content.size()]; + + for (int i = 0; i != result.length; i++) + { + result[i] = InfoTypeAndValue.getInstance(content.getObjectAt(i)); + } + + return result; + } + + /** + *
+     * GenMsgContent ::= SEQUENCE OF InfoTypeAndValue
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + return content; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/GenRepContent.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/GenRepContent.java new file mode 100644 index 000000000..2122b8044 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/GenRepContent.java @@ -0,0 +1,54 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class GenRepContent + extends ASN1Encodable +{ + private ASN1Sequence content; + + private GenRepContent(ASN1Sequence seq) + { + content = seq; + } + + public static GenRepContent getInstance(Object o) + { + if (o instanceof GenRepContent) + { + return (GenRepContent)o; + } + + if (o instanceof ASN1Sequence) + { + return new GenRepContent((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public InfoTypeAndValue[] toInfoTypeAndValueArray() + { + InfoTypeAndValue[] result = new InfoTypeAndValue[content.size()]; + + for (int i = 0; i != result.length; i++) + { + result[i] = InfoTypeAndValue.getInstance(content.getObjectAt(i)); + } + + return result; + } + + /** + *
+     * GenRepContent ::= SEQUENCE OF InfoTypeAndValue
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + return content; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/InfoTypeAndValue.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/InfoTypeAndValue.java new file mode 100644 index 000000000..8d4f59f34 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/InfoTypeAndValue.java @@ -0,0 +1,116 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * Example InfoTypeAndValue contents include, but are not limited + * to, the following (un-comment in this ASN.1 module and use as + * appropriate for a given environment): + *
+ *   id-it-caProtEncCert    OBJECT IDENTIFIER ::= {id-it 1}
+ *      CAProtEncCertValue      ::= CMPCertificate
+ *   id-it-signKeyPairTypes OBJECT IDENTIFIER ::= {id-it 2}
+ *     SignKeyPairTypesValue   ::= SEQUENCE OF AlgorithmIdentifier
+ *   id-it-encKeyPairTypes  OBJECT IDENTIFIER ::= {id-it 3}
+ *     EncKeyPairTypesValue    ::= SEQUENCE OF AlgorithmIdentifier
+ *   id-it-preferredSymmAlg OBJECT IDENTIFIER ::= {id-it 4}
+ *      PreferredSymmAlgValue   ::= AlgorithmIdentifier
+ *   id-it-caKeyUpdateInfo  OBJECT IDENTIFIER ::= {id-it 5}
+ *      CAKeyUpdateInfoValue    ::= CAKeyUpdAnnContent
+ *   id-it-currentCRL       OBJECT IDENTIFIER ::= {id-it 6}
+ *      CurrentCRLValue         ::= CertificateList
+ *   id-it-unsupportedOIDs  OBJECT IDENTIFIER ::= {id-it 7}
+ *      UnsupportedOIDsValue    ::= SEQUENCE OF OBJECT IDENTIFIER
+ *   id-it-keyPairParamReq  OBJECT IDENTIFIER ::= {id-it 10}
+ *      KeyPairParamReqValue    ::= OBJECT IDENTIFIER
+ *   id-it-keyPairParamRep  OBJECT IDENTIFIER ::= {id-it 11}
+ *      KeyPairParamRepValue    ::= AlgorithmIdentifer
+ *   id-it-revPassphrase    OBJECT IDENTIFIER ::= {id-it 12}
+ *      RevPassphraseValue      ::= EncryptedValue
+ *   id-it-implicitConfirm  OBJECT IDENTIFIER ::= {id-it 13}
+ *      ImplicitConfirmValue    ::= NULL
+ *   id-it-confirmWaitTime  OBJECT IDENTIFIER ::= {id-it 14}
+ *      ConfirmWaitTimeValue    ::= GeneralizedTime
+ *   id-it-origPKIMessage   OBJECT IDENTIFIER ::= {id-it 15}
+ *      OrigPKIMessageValue     ::= PKIMessages
+ *   id-it-suppLangTags     OBJECT IDENTIFIER ::= {id-it 16}
+ *      SuppLangTagsValue       ::= SEQUENCE OF UTF8String
+ *
+ * where
+ *
+ *   id-pkix OBJECT IDENTIFIER ::= {
+ *      iso(1) identified-organization(3)
+ *      dod(6) internet(1) security(5) mechanisms(5) pkix(7)}
+ * and
+ *      id-it   OBJECT IDENTIFIER ::= {id-pkix 4}
+ * 
+ */ +public class InfoTypeAndValue + extends ASN1Encodable +{ + private DERObjectIdentifier infoType; + private ASN1Encodable infoValue; + + private InfoTypeAndValue(ASN1Sequence seq) + { + infoType = DERObjectIdentifier.getInstance(seq.getObjectAt(0)); + + if (seq.size() > 1) + { + infoValue = (ASN1Encodable)seq.getObjectAt(1); + } + } + + public static InfoTypeAndValue getInstance(Object o) + { + if (o instanceof InfoTypeAndValue) + { + return (InfoTypeAndValue)o; + } + + if (o instanceof ASN1Sequence) + { + return new InfoTypeAndValue((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public DERObjectIdentifier getInfoType() + { + return infoType; + } + + public ASN1Encodable getInfoValue() + { + return infoValue; + } + + /** + *
+     * InfoTypeAndValue ::= SEQUENCE {
+     *                         infoType               OBJECT IDENTIFIER,
+     *                         infoValue              ANY DEFINED BY infoType  OPTIONAL
+     * }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(infoType); + + if (infoValue != null) + { + v.add(infoValue); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/KeyRecRepContent.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/KeyRecRepContent.java new file mode 100644 index 000000000..256922a66 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/KeyRecRepContent.java @@ -0,0 +1,141 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +import java.util.Enumeration; + +public class KeyRecRepContent + extends ASN1Encodable +{ + private PKIStatusInfo status; + private CMPCertificate newSigCert; + private ASN1Sequence caCerts; + private ASN1Sequence keyPairHist; + + private KeyRecRepContent(ASN1Sequence seq) + { + Enumeration en = seq.getObjects(); + + status = PKIStatusInfo.getInstance(en.nextElement()); + + while (en.hasMoreElements()) + { + ASN1TaggedObject tObj = ASN1TaggedObject.getInstance(en.nextElement()); + + switch (tObj.getTagNo()) + { + case 0: + newSigCert = CMPCertificate.getInstance(tObj.getObject()); + break; + case 1: + caCerts = ASN1Sequence.getInstance(tObj.getObject()); + break; + case 2: + keyPairHist = ASN1Sequence.getInstance(tObj.getObject()); + break; + default: + throw new IllegalArgumentException("unknown tag number: " + tObj.getTagNo()); + } + } + } + + public static KeyRecRepContent getInstance(Object o) + { + if (o instanceof KeyRecRepContent) + { + return (KeyRecRepContent)o; + } + + if (o instanceof ASN1Sequence) + { + return new KeyRecRepContent((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + + public PKIStatusInfo getStatus() + { + return status; + } + + public CMPCertificate getNewSigCert() + { + return newSigCert; + } + + public CMPCertificate[] getCaCerts() + { + if (caCerts == null) + { + return null; + } + + CMPCertificate[] results = new CMPCertificate[caCerts.size()]; + + for (int i = 0; i != results.length; i++) + { + results[i] = CMPCertificate.getInstance(caCerts.getObjectAt(i)); + } + + return results; + } + + public CertifiedKeyPair[] getKeyPairHist() + { + if (keyPairHist == null) + { + return null; + } + + CertifiedKeyPair[] results = new CertifiedKeyPair[keyPairHist.size()]; + + for (int i = 0; i != results.length; i++) + { + results[i] = CertifiedKeyPair.getInstance(keyPairHist.getObjectAt(i)); + } + + return results; + } + + /** + *
+     * KeyRecRepContent ::= SEQUENCE {
+     *                         status                  PKIStatusInfo,
+     *                         newSigCert          [0] CMPCertificate OPTIONAL,
+     *                         caCerts             [1] SEQUENCE SIZE (1..MAX) OF
+     *                                                           CMPCertificate OPTIONAL,
+     *                         keyPairHist         [2] SEQUENCE SIZE (1..MAX) OF
+     *                                                           CertifiedKeyPair OPTIONAL
+     *              }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(status); + + addOptional(v, 0, newSigCert); + addOptional(v, 1, caCerts); + addOptional(v, 2, keyPairHist); + + return new DERSequence(v); + } + + private void addOptional(ASN1EncodableVector v, int tagNo, ASN1Encodable obj) + { + if (obj != null) + { + v.add(new DERTaggedObject(true, tagNo, obj)); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/OOBCertHash.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/OOBCertHash.java new file mode 100644 index 000000000..72d19594f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/OOBCertHash.java @@ -0,0 +1,99 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.crmf.CertId; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class OOBCertHash + extends ASN1Encodable +{ + private AlgorithmIdentifier hashAlg; + private CertId certId; + private DERBitString hashVal; + + private OOBCertHash(ASN1Sequence seq) + { + int index = seq.size() - 1; + + hashVal = DERBitString.getInstance(seq.getObjectAt(index--)); + + for (int i = index; i >= 0; i--) + { + ASN1TaggedObject tObj = (ASN1TaggedObject)seq.getObjectAt(i); + + if (tObj.getTagNo() == 0) + { + hashAlg = AlgorithmIdentifier.getInstance(tObj, true); + } + else + { + certId = CertId.getInstance(tObj, true); + } + } + + } + + public static OOBCertHash getInstance(Object o) + { + if (o instanceof OOBCertHash) + { + return (OOBCertHash)o; + } + + if (o instanceof ASN1Sequence) + { + return new OOBCertHash((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public AlgorithmIdentifier getHashAlg() + { + return hashAlg; + } + + public CertId getCertId() + { + return certId; + } + + /** + *
+     * OOBCertHash ::= SEQUENCE {
+     *                      hashAlg     [0] AlgorithmIdentifier     OPTIONAL,
+     *                      certId      [1] CertId                  OPTIONAL,
+     *                      hashVal         BIT STRING
+     *                      -- hashVal is calculated over the DER encoding of the
+     *                      -- self-signed certificate with the identifier certID.
+     *       }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + addOptional(v, 0, hashAlg); + addOptional(v, 1, certId); + + v.add(hashVal); + + return new DERSequence(v); + } + + private void addOptional(ASN1EncodableVector v, int tagNo, ASN1Encodable obj) + { + if (obj != null) + { + v.add(new DERTaggedObject(true, tagNo, obj)); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/PBMParameter.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PBMParameter.java new file mode 100644 index 000000000..89c351f58 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PBMParameter.java @@ -0,0 +1,89 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class PBMParameter + extends ASN1Encodable +{ + private ASN1OctetString salt; + private AlgorithmIdentifier owf; + private DERInteger iterationCount; + private AlgorithmIdentifier mac; + + private PBMParameter(ASN1Sequence seq) + { + salt = ASN1OctetString.getInstance(seq.getObjectAt(0)); + owf = AlgorithmIdentifier.getInstance(seq.getObjectAt(1)); + iterationCount = DERInteger.getInstance(seq.getObjectAt(2)); + mac = AlgorithmIdentifier.getInstance(seq.getObjectAt(3)); + } + + public static PBMParameter getInstance(Object o) + { + if (o instanceof PBMParameter) + { + return (PBMParameter)o; + } + + if (o instanceof ASN1Sequence) + { + return new PBMParameter((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public AlgorithmIdentifier getOwf() + { + return owf; + } + + public DERInteger getIterationCount() + { + return iterationCount; + } + + public AlgorithmIdentifier getMac() + { + return mac; + } + + /** + *
+     *  PBMParameter ::= SEQUENCE {
+     *                        salt                OCTET STRING,
+     *                        -- note:  implementations MAY wish to limit acceptable sizes
+     *                        -- of this string to values appropriate for their environment
+     *                        -- in order to reduce the risk of denial-of-service attacks
+     *                        owf                 AlgorithmIdentifier,
+     *                        -- AlgId for a One-Way Function (SHA-1 recommended)
+     *                        iterationCount      INTEGER,
+     *                        -- number of times the OWF is applied
+     *                        -- note:  implementations MAY wish to limit acceptable sizes
+     *                        -- of this integer to values appropriate for their environment
+     *                        -- in order to reduce the risk of denial-of-service attacks
+     *                        mac                 AlgorithmIdentifier
+     *                        -- the MAC AlgId (e.g., DES-MAC, Triple-DES-MAC [PKCS11],
+     *    }   -- or HMAC [RFC2104, RFC2202])
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(salt); + v.add(owf); + v.add(iterationCount); + v.add(mac); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIBody.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIBody.java new file mode 100644 index 000000000..0462223f4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIBody.java @@ -0,0 +1,163 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.crmf.CertReqMessages; +import com.google.bitcoin.bouncycastle.asn1.pkcs.CertificationRequest; + +public class PKIBody + extends ASN1Encodable + implements ASN1Choice +{ + private int tagNo; + private ASN1Encodable body; + + public static PKIBody getInstance(Object o) + { + if (o instanceof PKIBody) + { + return (PKIBody)o; + } + + if (o instanceof ASN1TaggedObject) + { + return new PKIBody((ASN1TaggedObject)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + private PKIBody(ASN1TaggedObject tagged) + { + tagNo = tagged.getTagNo(); + + switch (tagged.getTagNo()) + { + case 0: + body = CertReqMessages.getInstance(tagged.getObject()); + break; + case 1: + body = CertRepMessage.getInstance(tagged.getObject()); + break; + case 2: + body = CertReqMessages.getInstance(tagged.getObject()); + break; + case 3: + body = CertRepMessage.getInstance(tagged.getObject()); + break; + case 4: + body = CertificationRequest.getInstance(tagged.getObject()); + break; + case 5: + body = POPODecKeyChallContent.getInstance(tagged.getObject()); + break; + case 6: + body = POPODecKeyRespContent.getInstance(tagged.getObject()); + break; + case 7: + body = CertReqMessages.getInstance(tagged.getObject()); + break; + case 8: + body = CertRepMessage.getInstance(tagged.getObject()); + break; + case 9: + body = CertReqMessages.getInstance(tagged.getObject()); + break; + case 10: + body = KeyRecRepContent.getInstance(tagged.getObject()); + break; + case 11: + body = RevReqContent.getInstance(tagged.getObject()); + break; + case 12: + body = RevRepContent.getInstance(tagged.getObject()); + break; + case 13: + body = CertReqMessages.getInstance(tagged.getObject()); + break; + case 14: + body = CertRepMessage.getInstance(tagged.getObject()); + break; + case 15: + body = CAKeyUpdAnnContent.getInstance(tagged.getObject()); + break; + case 16: + body = CMPCertificate.getInstance(tagged.getObject()); // CertAnnContent + break; + case 17: + body = RevAnnContent.getInstance(tagged.getObject()); + break; + case 18: + body = CRLAnnContent.getInstance(tagged.getObject()); + break; + case 19: + body = PKIConfirmContent.getInstance(tagged.getObject()); + break; + case 20: + body = PKIMessages.getInstance(tagged.getObject()); // NestedMessageContent + break; + case 21: + body = GenMsgContent.getInstance(tagged.getObject()); + break; + case 22: + body = GenRepContent.getInstance(tagged.getObject()); + break; + case 23: + body = ErrorMsgContent.getInstance(tagged.getObject()); + break; + case 24: + body = CertConfirmContent.getInstance(tagged.getObject()); + break; + case 25: + body = PollReqContent.getInstance(tagged.getObject()); + break; + case 26: + body = PollRepContent.getInstance(tagged.getObject()); + break; + default: + throw new IllegalArgumentException("unknown tag number: " + tagged.getTagNo()); + } + } + + /** + *
+     * PKIBody ::= CHOICE {       -- message-specific body elements
+     *        ir       [0]  CertReqMessages,        --Initialization Request
+     *        ip       [1]  CertRepMessage,         --Initialization Response
+     *        cr       [2]  CertReqMessages,        --Certification Request
+     *        cp       [3]  CertRepMessage,         --Certification Response
+     *        p10cr    [4]  CertificationRequest,   --imported from [PKCS10]
+     *        popdecc  [5]  POPODecKeyChallContent, --pop Challenge
+     *        popdecr  [6]  POPODecKeyRespContent,  --pop Response
+     *        kur      [7]  CertReqMessages,        --Key Update Request
+     *        kup      [8]  CertRepMessage,         --Key Update Response
+     *        krr      [9]  CertReqMessages,        --Key Recovery Request
+     *        krp      [10] KeyRecRepContent,       --Key Recovery Response
+     *        rr       [11] RevReqContent,          --Revocation Request
+     *        rp       [12] RevRepContent,          --Revocation Response
+     *        ccr      [13] CertReqMessages,        --Cross-Cert. Request
+     *        ccp      [14] CertRepMessage,         --Cross-Cert. Response
+     *        ckuann   [15] CAKeyUpdAnnContent,     --CA Key Update Ann.
+     *        cann     [16] CertAnnContent,         --Certificate Ann.
+     *        rann     [17] RevAnnContent,          --Revocation Ann.
+     *        crlann   [18] CRLAnnContent,          --CRL Announcement
+     *        pkiconf  [19] PKIConfirmContent,      --Confirmation
+     *        nested   [20] NestedMessageContent,   --Nested Message
+     *        genm     [21] GenMsgContent,          --General Message
+     *        genp     [22] GenRepContent,          --General Response
+     *        error    [23] ErrorMsgContent,        --Error Message
+     *        certConf [24] CertConfirmContent,     --Certificate confirm
+     *        pollReq  [25] PollReqContent,         --Polling request
+     *        pollRep  [26] PollRepContent          --Polling response
+     * }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + return new DERTaggedObject(true, tagNo, body); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIConfirmContent.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIConfirmContent.java new file mode 100644 index 000000000..7337e9cc1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIConfirmContent.java @@ -0,0 +1,42 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Null; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class PKIConfirmContent + extends ASN1Encodable +{ + private ASN1Null val; + + private PKIConfirmContent(ASN1Null val) + { + this.val = val; + } + + public static PKIConfirmContent getInstance(Object o) + { + if (o instanceof PKIConfirmContent) + { + return (PKIConfirmContent)o; + } + + if (o instanceof ASN1Null) + { + return new PKIConfirmContent((ASN1Null)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + /** + *
+     * PKIConfirmContent ::= NULL
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + return val; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIFailureInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIFailureInfo.java new file mode 100644 index 000000000..679d0bfca --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIFailureInfo.java @@ -0,0 +1,104 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.DERBitString; + +/** + *
+ * PKIFailureInfo ::= BIT STRING {
+ * badAlg               (0),
+ *   -- unrecognized or unsupported Algorithm Identifier
+ * badMessageCheck      (1), -- integrity check failed (e.g., signature did not verify)
+ * badRequest           (2),
+ *   -- transaction not permitted or supported
+ * badTime              (3), -- messageTime was not sufficiently close to the system time, as defined by local policy
+ * badCertId            (4), -- no certificate could be found matching the provided criteria
+ * badDataFormat        (5),
+ *   -- the data submitted has the wrong format
+ * wrongAuthority       (6), -- the authority indicated in the request is different from the one creating the response token
+ * incorrectData        (7), -- the requester's data is incorrect (for notary services)
+ * missingTimeStamp     (8), -- when the timestamp is missing but should be there (by policy)
+ * badPOP               (9)  -- the proof-of-possession failed
+ * timeNotAvailable    (14),
+ *   -- the TSA's time source is not available
+ * unacceptedPolicy    (15),
+ *   -- the requested TSA policy is not supported by the TSA
+ * unacceptedExtension (16),
+ *   -- the requested extension is not supported by the TSA
+ *  addInfoNotAvailable (17)
+ *    -- the additional information requested could not be understood
+ *    -- or is not available
+ *  systemFailure       (25)
+ *    -- the request cannot be handled due to system failure 
+ * 
+ */ +public class PKIFailureInfo + extends DERBitString +{ + + + public static final int badAlg = (1 << 7); // unrecognized or unsupported Algorithm Identifier + public static final int badMessageCheck = (1 << 6); // integrity check failed (e.g., signature did not verify) + public static final int badRequest = (1 << 5); + public static final int badTime = (1 << 4); // -- messageTime was not sufficiently close to the system time, as defined by local policy + public static final int badCertId = (1 << 3); // no certificate could be found matching the provided criteria + public static final int badDataFormat = (1 << 2); + public static final int wrongAuthority = (1 << 1); // the authority indicated in the request is different from the one creating the response token + public static final int incorrectData = 1; // the requester's data is incorrect (for notary services) + public static final int missingTimeStamp = (1 << 15); // when the timestamp is missing but should be there (by policy) + public static final int badPOP = (1 << 14); // the proof-of-possession failed + public static final int timeNotAvailable = (1 << 9); // the TSA's time source is not available + public static final int unacceptedPolicy = (1 << 8); // the requested TSA policy is not supported by the TSA + public static final int unacceptedExtension = (1 << 23); //the requested extension is not supported by the TSA + public static final int addInfoNotAvailable = (1 << 22); //the additional information requested could not be understood or is not available + public static final int systemFailure = (1 << 30); //the request cannot be handled due to system failure + + /** @deprecated use lower case version */ + public static final int BAD_ALG = badAlg; // unrecognized or unsupported Algorithm Identifier + /** @deprecated use lower case version */ + public static final int BAD_MESSAGE_CHECK = badMessageCheck; + /** @deprecated use lower case version */ + public static final int BAD_REQUEST = badRequest; // transaction not permitted or supported + /** @deprecated use lower case version */ + public static final int BAD_TIME = badTime; + /** @deprecated use lower case version */ + public static final int BAD_CERT_ID = badCertId; + /** @deprecated use lower case version */ + public static final int BAD_DATA_FORMAT = badDataFormat; // the data submitted has the wrong format + /** @deprecated use lower case version */ + public static final int WRONG_AUTHORITY = wrongAuthority; + /** @deprecated use lower case version */ + public static final int INCORRECT_DATA = incorrectData; + /** @deprecated use lower case version */ + public static final int MISSING_TIME_STAMP = missingTimeStamp; + /** @deprecated use lower case version */ + public static final int BAD_POP = badPOP; + /** @deprecated use lower case version */ + public static final int TIME_NOT_AVAILABLE = timeNotAvailable; + /** @deprecated use lower case version */ + public static final int UNACCEPTED_POLICY = unacceptedPolicy; + /** @deprecated use lower case version */ + public static final int UNACCEPTED_EXTENSION = unacceptedExtension; + /** @deprecated use lower case version */ + public static final int ADD_INFO_NOT_AVAILABLE = addInfoNotAvailable; + /** @deprecated use lower case version */ + public static final int SYSTEM_FAILURE = systemFailure; + /** + * Basic constructor. + */ + public PKIFailureInfo( + int info) + { + super(getBytes(info), getPadBits(info)); + } + + public PKIFailureInfo( + DERBitString info) + { + super(info.getBytes(), info.getPadBits()); + } + + public String toString() + { + return "PKIFailureInfo: 0x" + Integer.toHexString(this.intValue()); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIFreeText.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIFreeText.java new file mode 100644 index 000000000..654940797 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIFreeText.java @@ -0,0 +1,91 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERUTF8String; + +public class PKIFreeText + extends ASN1Encodable +{ + ASN1Sequence strings; + + public static PKIFreeText getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static PKIFreeText getInstance( + Object obj) + { + if (obj instanceof PKIFreeText) + { + return (PKIFreeText)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new PKIFreeText((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Unknown object in factory: " + obj.getClass().getName()); + } + + public PKIFreeText( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + while (e.hasMoreElements()) + { + if (!(e.nextElement() instanceof DERUTF8String)) + { + throw new IllegalArgumentException("attempt to insert non UTF8 STRING into PKIFreeText"); + } + } + + strings = seq; + } + + public PKIFreeText( + DERUTF8String p) + { + strings = new DERSequence(p); + } + + /** + * Return the number of string elements present. + * + * @return number of elements present. + */ + public int size() + { + return strings.size(); + } + + /** + * Return the UTF8STRING at index i. + * + * @param i index of the string of interest + * @return the string at index i. + */ + public DERUTF8String getStringAt( + int i) + { + return (DERUTF8String)strings.getObjectAt(i); + } + + /** + *
+     * PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String
+     * 
+ */ + public DERObject toASN1Object() + { + return strings; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIHeader.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIHeader.java new file mode 100644 index 000000000..201caa0c2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIHeader.java @@ -0,0 +1,176 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; +import com.google.bitcoin.bouncycastle.asn1.x509.GeneralName; + +import java.util.Enumeration; + +public class PKIHeader + extends ASN1Encodable +{ + private DERInteger pvno; + private GeneralName sender; + private GeneralName recipient; + private DERGeneralizedTime messageTime; + private AlgorithmIdentifier protectionAlg; + private ASN1OctetString senderKID; // KeyIdentifier + private ASN1OctetString recipKID; // KeyIdentifier + private ASN1OctetString transactionID; + private ASN1OctetString senderNonce; + private ASN1OctetString recipNonce; + private PKIFreeText freeText; + private ASN1Sequence generalInfo; + + private PKIHeader(ASN1Sequence seq) + { + Enumeration en = seq.getObjects(); + + pvno = DERInteger.getInstance(en.nextElement()); + sender = GeneralName.getInstance(en.nextElement()); + recipient = GeneralName.getInstance(en.nextElement()); + + while (en.hasMoreElements()) + { + ASN1TaggedObject tObj = (ASN1TaggedObject)en.nextElement(); + + switch (tObj.getTagNo()) + { + case 0: + messageTime = DERGeneralizedTime.getInstance(tObj, true); + break; + case 1: + protectionAlg = AlgorithmIdentifier.getInstance(tObj, true); + break; + case 2: + senderKID = ASN1OctetString.getInstance(tObj, true); + break; + case 3: + recipKID = ASN1OctetString.getInstance(tObj, true); + break; + case 4: + transactionID = ASN1OctetString.getInstance(tObj, true); + break; + case 5: + senderNonce = ASN1OctetString.getInstance(tObj, true); + break; + case 6: + recipNonce = ASN1OctetString.getInstance(tObj, true); + break; + case 7: + freeText = PKIFreeText.getInstance(tObj, true); + break; + case 8: + generalInfo = ASN1Sequence.getInstance(tObj, true); + break; + default: + throw new IllegalArgumentException("unknown tag number: " + tObj.getTagNo()); + } + } + } + + public static PKIHeader getInstance(Object o) + { + if (o instanceof PKIHeader) + { + return (PKIHeader)o; + } + + if (o instanceof ASN1Sequence) + { + return new PKIHeader((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public DERInteger getPvno() + { + return pvno; + } + + public GeneralName getSender() + { + return sender; + } + + public GeneralName getRecipient() + { + return recipient; + } + + /** + *
+     *  PKIHeader ::= SEQUENCE {
+     *            pvno                INTEGER     { cmp1999(1), cmp2000(2) },
+     *            sender              GeneralName,
+     *            -- identifies the sender
+     *            recipient           GeneralName,
+     *            -- identifies the intended recipient
+     *            messageTime     [0] GeneralizedTime         OPTIONAL,
+     *            -- time of production of this message (used when sender
+     *            -- believes that the transport will be "suitable"; i.e.,
+     *            -- that the time will still be meaningful upon receipt)
+     *            protectionAlg   [1] AlgorithmIdentifier     OPTIONAL,
+     *            -- algorithm used for calculation of protection bits
+     *            senderKID       [2] KeyIdentifier           OPTIONAL,
+     *            recipKID        [3] KeyIdentifier           OPTIONAL,
+     *            -- to identify specific keys used for protection
+     *            transactionID   [4] OCTET STRING            OPTIONAL,
+     *            -- identifies the transaction; i.e., this will be the same in
+     *            -- corresponding request, response, certConf, and PKIConf
+     *            -- messages
+     *            senderNonce     [5] OCTET STRING            OPTIONAL,
+     *            recipNonce      [6] OCTET STRING            OPTIONAL,
+     *            -- nonces used to provide replay protection, senderNonce
+     *            -- is inserted by the creator of this message; recipNonce
+     *            -- is a nonce previously inserted in a related message by
+     *            -- the intended recipient of this message
+     *            freeText        [7] PKIFreeText             OPTIONAL,
+     *            -- this may be used to indicate context-specific instructions
+     *            -- (this field is intended for human consumption)
+     *            generalInfo     [8] SEQUENCE SIZE (1..MAX) OF
+     *                                 InfoTypeAndValue     OPTIONAL
+     *            -- this may be used to convey context-specific information
+     *            -- (this field not primarily intended for human consumption)
+     * }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(pvno); + v.add(sender); + v.add(recipient); + addOptional(v, 0, messageTime); + addOptional(v, 1, protectionAlg); + addOptional(v, 2, senderKID); + addOptional(v, 3, recipKID); + addOptional(v, 4, transactionID); + addOptional(v, 5, senderNonce); + addOptional(v, 6, recipNonce); + addOptional(v, 7, freeText); + addOptional(v, 8, generalInfo); + + return new DERSequence(v); + } + + private void addOptional(ASN1EncodableVector v, int tagNo, ASN1Encodable obj) + { + if (obj != null) + { + v.add(new DERTaggedObject(true, tagNo, obj)); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIMessage.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIMessage.java new file mode 100644 index 000000000..5ebf32b3f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIMessage.java @@ -0,0 +1,101 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +import java.util.Enumeration; + +public class PKIMessage + extends ASN1Encodable +{ + private PKIHeader header; + private PKIBody body; + private DERBitString protection; + private ASN1Sequence extraCerts; + + private PKIMessage(ASN1Sequence seq) + { + Enumeration en = seq.getObjects(); + + header = PKIHeader.getInstance(en.nextElement()); + body = PKIBody.getInstance(en.nextElement()); + + while (en.hasMoreElements()) + { + ASN1TaggedObject tObj = (ASN1TaggedObject)en.nextElement(); + + if (tObj.getTagNo() == 0) + { + protection = DERBitString.getInstance(tObj, true); + } + else + { + extraCerts = ASN1Sequence.getInstance(tObj, true); + } + } + } + + public static PKIMessage getInstance(Object o) + { + if (o instanceof PKIMessage) + { + return (PKIMessage)o; + } + + if (o instanceof ASN1Sequence) + { + return new PKIMessage((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public PKIHeader getHeader() + { + return header; + } + + public PKIBody getBody() + { + return body; + } + + /** + *
+     * PKIMessage ::= SEQUENCE {
+     *                  header           PKIHeader,
+     *                  body             PKIBody,
+     *                  protection   [0] PKIProtection OPTIONAL,
+     *                  extraCerts   [1] SEQUENCE SIZE (1..MAX) OF CMPCertificate
+     *                                                                     OPTIONAL
+     * }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(header); + v.add(body); + + addOptional(v, 0, protection); + addOptional(v, 1, extraCerts); + + return new DERSequence(v); + } + + private void addOptional(ASN1EncodableVector v, int tagNo, ASN1Encodable obj) + { + if (obj != null) + { + v.add(new DERTaggedObject(true, tagNo, obj)); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIMessages.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIMessages.java new file mode 100644 index 000000000..ed9f0e95d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIMessages.java @@ -0,0 +1,54 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class PKIMessages + extends ASN1Encodable +{ + private ASN1Sequence content; + + private PKIMessages(ASN1Sequence seq) + { + content = seq; + } + + public static PKIMessages getInstance(Object o) + { + if (o instanceof PKIMessages) + { + return (PKIMessages)o; + } + + if (o instanceof ASN1Sequence) + { + return new PKIMessages((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public PKIMessage[] toPKIMessageArray() + { + PKIMessage[] result = new PKIMessage[content.size()]; + + for (int i = 0; i != result.length; i++) + { + result[i] = PKIMessage.getInstance(content.getObjectAt(i)); + } + + return result; + } + + /** + *
+     * PKIMessages ::= SEQUENCE SIZE (1..MAX) OF PKIMessage
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + return content; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIStatus.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIStatus.java new file mode 100644 index 000000000..82075a589 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIStatus.java @@ -0,0 +1,57 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class PKIStatus + extends ASN1Encodable +{ + public static final int GRANTED = 0; + public static final int GRANTED_WITH_MODS = 1; + public static final int REJECTION = 2; + public static final int WAITING = 3; + public static final int REVOCATION_WARNING = 4; + public static final int REVOCATION_NOTIFICATION = 5; + public static final int KEY_UPDATE_WARNING = 6; + + public static final PKIStatus granted = new PKIStatus(GRANTED); + public static final PKIStatus grantedWithMods = new PKIStatus(GRANTED_WITH_MODS); + public static final PKIStatus rejection = new PKIStatus(REJECTION); + public static final PKIStatus waiting = new PKIStatus(WAITING); + public static final PKIStatus revocationWarning = new PKIStatus(REVOCATION_WARNING); + public static final PKIStatus revocationNotification = new PKIStatus(REVOCATION_NOTIFICATION); + public static final PKIStatus keyUpdateWaiting = new PKIStatus(KEY_UPDATE_WARNING); + + private DERInteger value; + + private PKIStatus(int value) + { + this(new DERInteger(value)); + } + + private PKIStatus(DERInteger value) + { + this.value = value; + } + + public static PKIStatus getInstance(Object o) + { + if (o instanceof PKIStatus) + { + return (PKIStatus)o; + } + + if (o instanceof DERInteger) + { + return new PKIStatus((DERInteger)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public DERObject toASN1Object() + { + return value; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIStatusInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIStatusInfo.java new file mode 100644 index 000000000..c88e265c8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PKIStatusInfo.java @@ -0,0 +1,164 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class PKIStatusInfo + extends ASN1Encodable +{ + DERInteger status; + PKIFreeText statusString; + DERBitString failInfo; + + public static PKIStatusInfo getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static PKIStatusInfo getInstance( + Object obj) + { + if (obj instanceof PKIStatusInfo) + { + return (PKIStatusInfo)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new PKIStatusInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public PKIStatusInfo( + ASN1Sequence seq) + { + this.status = DERInteger.getInstance(seq.getObjectAt(0)); + + this.statusString = null; + this.failInfo = null; + + if (seq.size() > 2) + { + this.statusString = PKIFreeText.getInstance(seq.getObjectAt(1)); + this.failInfo = DERBitString.getInstance(seq.getObjectAt(2)); + } + else if (seq.size() > 1) + { + Object obj = seq.getObjectAt(1); + if (obj instanceof DERBitString) + { + this.failInfo = DERBitString.getInstance(obj); + } + else + { + this.statusString = PKIFreeText.getInstance(obj); + } + } + } + + /** + * @param status + */ + public PKIStatusInfo(int status) + { + this.status = new DERInteger(status); + } + + /** + * @param status + * @param statusString + */ + public PKIStatusInfo( + int status, + PKIFreeText statusString) + { + this.status = new DERInteger(status); + this.statusString = statusString; + } + + public PKIStatusInfo( + int status, + PKIFreeText statusString, + PKIFailureInfo failInfo) + { + this.status = new DERInteger(status); + this.statusString = statusString; + this.failInfo = failInfo; + } + + public BigInteger getStatus() + { + return status.getValue(); + } + + public PKIFreeText getStatusString() + { + return statusString; + } + + public DERBitString getFailInfo() + { + return failInfo; + } + + /** + *
+     * PKIStatusInfo ::= SEQUENCE {
+     *     status        PKIStatus,                (INTEGER)
+     *     statusString  PKIFreeText     OPTIONAL,
+     *     failInfo      PKIFailureInfo  OPTIONAL  (BIT STRING)
+     * }
+     *
+     * PKIStatus:
+     *   granted                (0), -- you got exactly what you asked for
+     *   grantedWithMods        (1), -- you got something like what you asked for
+     *   rejection              (2), -- you don't get it, more information elsewhere in the message
+     *   waiting                (3), -- the request body part has not yet been processed, expect to hear more later
+     *   revocationWarning      (4), -- this message contains a warning that a revocation is imminent
+     *   revocationNotification (5), -- notification that a revocation has occurred
+     *   keyUpdateWarning       (6)  -- update already done for the oldCertId specified in CertReqMsg
+     *
+     * PKIFailureInfo:
+     *   badAlg           (0), -- unrecognized or unsupported Algorithm Identifier
+     *   badMessageCheck  (1), -- integrity check failed (e.g., signature did not verify)
+     *   badRequest       (2), -- transaction not permitted or supported
+     *   badTime          (3), -- messageTime was not sufficiently close to the system time, as defined by local policy
+     *   badCertId        (4), -- no certificate could be found matching the provided criteria
+     *   badDataFormat    (5), -- the data submitted has the wrong format
+     *   wrongAuthority   (6), -- the authority indicated in the request is different from the one creating the response token
+     *   incorrectData    (7), -- the requester's data is incorrect (for notary services)
+     *   missingTimeStamp (8), -- when the timestamp is missing but should be there (by policy)
+     *   badPOP           (9)  -- the proof-of-possession failed
+     *
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(status); + + if (statusString != null) + { + v.add(statusString); + } + + if (failInfo!= null) + { + v.add(failInfo); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/POPODecKeyChallContent.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/POPODecKeyChallContent.java new file mode 100644 index 000000000..4320c1295 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/POPODecKeyChallContent.java @@ -0,0 +1,54 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class POPODecKeyChallContent + extends ASN1Encodable +{ + private ASN1Sequence content; + + private POPODecKeyChallContent(ASN1Sequence seq) + { + content = seq; + } + + public static POPODecKeyChallContent getInstance(Object o) + { + if (o instanceof POPODecKeyChallContent) + { + return (POPODecKeyChallContent)o; + } + + if (o instanceof ASN1Sequence) + { + return new POPODecKeyChallContent((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public Challenge[] toChallengeArray() + { + Challenge[] result = new Challenge[content.size()]; + + for (int i = 0; i != result.length; i++) + { + result[i] = Challenge.getInstance(content.getObjectAt(i)); + } + + return result; + } + + /** + *
+     * POPODecKeyChallContent ::= SEQUENCE OF Challenge
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + return content; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/POPODecKeyRespContent.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/POPODecKeyRespContent.java new file mode 100644 index 000000000..7f61755ed --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/POPODecKeyRespContent.java @@ -0,0 +1,55 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class POPODecKeyRespContent + extends ASN1Encodable +{ + private ASN1Sequence content; + + private POPODecKeyRespContent(ASN1Sequence seq) + { + content = seq; + } + + public static POPODecKeyRespContent getInstance(Object o) + { + if (o instanceof POPODecKeyRespContent) + { + return (POPODecKeyRespContent)o; + } + + if (o instanceof ASN1Sequence) + { + return new POPODecKeyRespContent((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public DERInteger[] toDERIntegerArray() + { + DERInteger[] result = new DERInteger[content.size()]; + + for (int i = 0; i != result.length; i++) + { + result[i] = DERInteger.getInstance(content.getObjectAt(i)); + } + + return result; + } + + /** + *
+     * POPODecKeyRespContent ::= SEQUENCE OF INTEGER
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + return content; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/PollRepContent.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PollRepContent.java new file mode 100644 index 000000000..d454fc4c5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PollRepContent.java @@ -0,0 +1,82 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class PollRepContent + extends ASN1Encodable +{ + private DERInteger certReqId; + private DERInteger checkAfter; + private PKIFreeText reason; + + private PollRepContent(ASN1Sequence seq) + { + certReqId = DERInteger.getInstance(seq.getObjectAt(0)); + checkAfter = DERInteger.getInstance(seq.getObjectAt(1)); + + if (seq.size() > 2) + { + reason = PKIFreeText.getInstance(seq.getObjectAt(2)); + } + } + + public static PollRepContent getInstance(Object o) + { + if (o instanceof PollRepContent) + { + return (PollRepContent)o; + } + + if (o instanceof ASN1Sequence) + { + return new PollRepContent((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public DERInteger getCertReqId() + { + return certReqId; + } + + public DERInteger getCheckAfter() + { + return checkAfter; + } + + public PKIFreeText getReason() + { + return reason; + } + + /** + *
+     * PollRepContent ::= SEQUENCE OF SEQUENCE {
+     *         certReqId              INTEGER,
+     *         checkAfter             INTEGER,  -- time in seconds
+     *         reason                 PKIFreeText OPTIONAL
+     *     }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(certReqId); + v.add(checkAfter); + + if (reason != null) + { + v.add(reason); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/PollReqContent.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PollReqContent.java new file mode 100644 index 000000000..a5223a285 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/PollReqContent.java @@ -0,0 +1,69 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class PollReqContent + extends ASN1Encodable +{ + private ASN1Sequence content; + + private PollReqContent(ASN1Sequence seq) + { + content = seq; + } + + public static PollReqContent getInstance(Object o) + { + if (o instanceof PollReqContent) + { + return (PollReqContent)o; + } + + if (o instanceof ASN1Sequence) + { + return new PollReqContent((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public DERInteger[][] getCertReqIds() + { + DERInteger[][] result = new DERInteger[content.size()][]; + + for (int i = 0; i != result.length; i++) + { + result[i] = seqenceToDERIntegerArray((ASN1Sequence)content.getObjectAt(i)); + } + + return result; + } + + private DERInteger[] seqenceToDERIntegerArray(ASN1Sequence seq) + { + DERInteger[] result = new DERInteger[seq.size()]; + + for (int i = 0; i != result.length; i++) + { + result[i] = DERInteger.getInstance(seq.getObjectAt(i)); + } + + return result; + } + + /** + *
+     * PollReqContent ::= SEQUENCE OF SEQUENCE {
+     *                        certReqId              INTEGER
+     * }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + return content; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/ProtectedPart.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/ProtectedPart.java new file mode 100644 index 000000000..d3c06de5e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/ProtectedPart.java @@ -0,0 +1,64 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class ProtectedPart + extends ASN1Encodable +{ + private PKIHeader header; + private PKIBody body; + + private ProtectedPart(ASN1Sequence seq) + { + header = PKIHeader.getInstance(seq.getObjectAt(0)); + body = PKIBody.getInstance(seq.getObjectAt(1)); + } + + public static ProtectedPart getInstance(Object o) + { + if (o instanceof ProtectedPart) + { + return (ProtectedPart)o; + } + + if (o instanceof ASN1Sequence) + { + return new ProtectedPart((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public PKIHeader getHeader() + { + return header; + } + + public PKIBody getBody() + { + return body; + } + + /** + *
+     * ProtectedPart ::= SEQUENCE {
+     *                    header    PKIHeader,
+     *                    body      PKIBody
+     * }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(header); + v.add(body); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/RevAnnContent.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/RevAnnContent.java new file mode 100644 index 000000000..3e2c8f3d1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/RevAnnContent.java @@ -0,0 +1,103 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.crmf.CertId; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Extensions; + +public class RevAnnContent + extends ASN1Encodable +{ + private PKIStatus status; + private CertId certId; + private DERGeneralizedTime willBeRevokedAt; + private DERGeneralizedTime badSinceDate; + private X509Extensions crlDetails; + + private RevAnnContent(ASN1Sequence seq) + { + status = PKIStatus.getInstance(seq.getObjectAt(0)); + certId = CertId.getInstance(seq.getObjectAt(1)); + willBeRevokedAt = DERGeneralizedTime.getInstance(seq.getObjectAt(2)); + badSinceDate = DERGeneralizedTime.getInstance(seq.getObjectAt(3)); + + if (seq.size() > 4) + { + crlDetails = X509Extensions.getInstance(seq.getObjectAt(4)); + } + } + + public static RevAnnContent getInstance(Object o) + { + if (o instanceof RevAnnContent) + { + return (RevAnnContent)o; + } + + if (o instanceof ASN1Sequence) + { + return new RevAnnContent((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public PKIStatus getStatus() + { + return status; + } + + public CertId getCertId() + { + return certId; + } + + public DERGeneralizedTime getWillBeRevokedAt() + { + return willBeRevokedAt; + } + + public DERGeneralizedTime getBadSinceDate() + { + return badSinceDate; + } + + public X509Extensions getCrlDetails() + { + return crlDetails; + } + + /** + *
+     * RevAnnContent ::= SEQUENCE {
+     *       status              PKIStatus,
+     *       certId              CertId,
+     *       willBeRevokedAt     GeneralizedTime,
+     *       badSinceDate        GeneralizedTime,
+     *       crlDetails          Extensions  OPTIONAL
+     *        -- extra CRL details (e.g., crl number, reason, location, etc.)
+     * }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(status); + v.add(certId); + v.add(willBeRevokedAt); + v.add(badSinceDate); + + if (crlDetails != null) + { + v.add(crlDetails); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/RevDetails.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/RevDetails.java new file mode 100644 index 000000000..511922565 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/RevDetails.java @@ -0,0 +1,77 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.crmf.CertTemplate; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Extensions; + +public class RevDetails + extends ASN1Encodable +{ + private CertTemplate certDetails; + private X509Extensions crlEntryDetails; + + private RevDetails(ASN1Sequence seq) + { + certDetails = CertTemplate.getInstance(seq.getObjectAt(0)); + if (seq.size() > 1) + { + crlEntryDetails = X509Extensions.getInstance(seq.getObjectAt(1)); + } + } + + public static RevDetails getInstance(Object o) + { + if (o instanceof RevDetails) + { + return (RevDetails)o; + } + + if (o instanceof ASN1Sequence) + { + return new RevDetails((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public CertTemplate getCertDetails() + { + return certDetails; + } + + public X509Extensions getCrlEntryDetails() + { + return crlEntryDetails; + } + + /** + *
+     * RevDetails ::= SEQUENCE {
+     *                  certDetails         CertTemplate,
+     *                   -- allows requester to specify as much as they can about
+     *                   -- the cert. for which revocation is requested
+     *                   -- (e.g., for cases in which serialNumber is not available)
+     *                   crlEntryDetails     Extensions       OPTIONAL
+     *                   -- requested crlEntryExtensions
+     *             }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(certDetails); + + if (crlEntryDetails != null) + { + v.add(crlEntryDetails); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/RevRepContent.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/RevRepContent.java new file mode 100644 index 000000000..2deba85fa --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/RevRepContent.java @@ -0,0 +1,136 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.crmf.CertId; +import com.google.bitcoin.bouncycastle.asn1.x509.CertificateList; + +import java.util.Enumeration; + +public class RevRepContent + extends ASN1Encodable +{ + private ASN1Sequence status; + private ASN1Sequence revCerts; + private ASN1Sequence crls; + + private RevRepContent(ASN1Sequence seq) + { + Enumeration en = seq.getObjects(); + + status = ASN1Sequence.getInstance(en.nextElement()); + while (en.hasMoreElements()) + { + ASN1TaggedObject tObj = ASN1TaggedObject.getInstance(en.nextElement()); + + if (tObj.getTagNo() == 0) + { + revCerts = ASN1Sequence.getInstance(tObj, true); + } + else + { + crls = ASN1Sequence.getInstance(tObj, true); + } + } + } + + public static RevRepContent getInstance(Object o) + { + if (o instanceof RevRepContent) + { + return (RevRepContent)o; + } + + if (o instanceof ASN1Sequence) + { + return new RevRepContent((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public PKIStatusInfo[] getStatus() + { + PKIStatusInfo[] results = new PKIStatusInfo[status.size()]; + + for (int i = 0; i != results.length; i++) + { + results[i] = PKIStatusInfo.getInstance(status.getObjectAt(i)); + } + + return results; + } + + public CertId[] getRevCerts() + { + if (revCerts == null) + { + return null; + } + + CertId[] results = new CertId[revCerts.size()]; + + for (int i = 0; i != results.length; i++) + { + results[i] = CertId.getInstance(revCerts.getObjectAt(i)); + } + + return results; + } + + public CertificateList[] getCrls() + { + if (crls == null) + { + return null; + } + + CertificateList[] results = new CertificateList[crls.size()]; + + for (int i = 0; i != results.length; i++) + { + results[i] = CertificateList.getInstance(crls.getObjectAt(i)); + } + + return results; + } + + /** + *
+     * RevRepContent ::= SEQUENCE {
+     *        status       SEQUENCE SIZE (1..MAX) OF PKIStatusInfo,
+     *        -- in same order as was sent in RevReqContent
+     *        revCerts [0] SEQUENCE SIZE (1..MAX) OF CertId OPTIONAL,
+     *        -- IDs for which revocation was requested
+     *        -- (same order as status)
+     *        crls     [1] SEQUENCE SIZE (1..MAX) OF CertificateList OPTIONAL
+     *        -- the resulting CRLs (there may be more than one)
+     *   }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(status); + + addOptional(v, 0, revCerts); + addOptional(v, 1, crls); + + return new DERSequence(v); + } + + private void addOptional(ASN1EncodableVector v, int tagNo, ASN1Encodable obj) + { + if (obj != null) + { + v.add(new DERTaggedObject(true, tagNo, obj)); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cmp/RevReqContent.java b/src/com/google/bitcoin/bouncycastle/asn1/cmp/RevReqContent.java new file mode 100644 index 000000000..c811f40fa --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cmp/RevReqContent.java @@ -0,0 +1,54 @@ +package com.google.bitcoin.bouncycastle.asn1.cmp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class RevReqContent + extends ASN1Encodable +{ + private ASN1Sequence content; + + private RevReqContent(ASN1Sequence seq) + { + content = seq; + } + + public static RevReqContent getInstance(Object o) + { + if (o instanceof RevReqContent) + { + return (RevReqContent)o; + } + + if (o instanceof ASN1Sequence) + { + return new RevReqContent((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public RevDetails[] toRevDetailsArray() + { + RevDetails[] result = new RevDetails[content.size()]; + + for (int i = 0; i != result.length; i++) + { + result[i] = RevDetails.getInstance(content.getObjectAt(i)); + } + + return result; + } + + /** + *
+     * RevReqContent ::= SEQUENCE OF RevDetails
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + return content; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/Attribute.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/Attribute.java new file mode 100644 index 000000000..210d950e5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/Attribute.java @@ -0,0 +1,82 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class Attribute + extends ASN1Encodable +{ + private DERObjectIdentifier attrType; + private ASN1Set attrValues; + + /** + * return an Attribute object from the given object. + * + * @param o the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static Attribute getInstance( + Object o) + { + if (o == null || o instanceof Attribute) + { + return (Attribute)o; + } + + if (o instanceof ASN1Sequence) + { + return new Attribute((ASN1Sequence)o); + } + + throw new IllegalArgumentException("unknown object in factory: " + o.getClass().getName()); + } + + public Attribute( + ASN1Sequence seq) + { + attrType = (DERObjectIdentifier)seq.getObjectAt(0); + attrValues = (ASN1Set)seq.getObjectAt(1); + } + + public Attribute( + DERObjectIdentifier attrType, + ASN1Set attrValues) + { + this.attrType = attrType; + this.attrValues = attrValues; + } + + public DERObjectIdentifier getAttrType() + { + return attrType; + } + + public ASN1Set getAttrValues() + { + return attrValues; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * Attribute ::= SEQUENCE {
+     *     attrType OBJECT IDENTIFIER,
+     *     attrValues SET OF AttributeValue
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(attrType); + v.add(attrValues); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/AttributeTable.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/AttributeTable.java new file mode 100644 index 000000000..b1581ec60 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/AttributeTable.java @@ -0,0 +1,173 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.DEREncodableVector; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public class AttributeTable +{ + private Hashtable attributes = new Hashtable(); + + public AttributeTable( + Hashtable attrs) + { + attributes = copyTable(attrs); + } + + public AttributeTable( + DEREncodableVector v) + { + for (int i = 0; i != v.size(); i++) + { + Attribute a = Attribute.getInstance(v.get(i)); + + addAttribute(a.getAttrType(), a); + } + } + + public AttributeTable( + ASN1Set s) + { + for (int i = 0; i != s.size(); i++) + { + Attribute a = Attribute.getInstance(s.getObjectAt(i)); + + addAttribute(a.getAttrType(), a); + } + } + + private void addAttribute( + DERObjectIdentifier oid, + Attribute a) + { + Object value = attributes.get(oid); + + if (value == null) + { + attributes.put(oid, a); + } + else + { + Vector v; + + if (value instanceof Attribute) + { + v = new Vector(); + + v.addElement(value); + v.addElement(a); + } + else + { + v = (Vector)value; + + v.addElement(a); + } + + attributes.put(oid, v); + } + } + + /** + * Return the first attribute matching the OBJECT IDENTIFIER oid. + * + * @param oid type of attribute required. + * @return first attribute found of type oid. + */ + public Attribute get( + DERObjectIdentifier oid) + { + Object value = attributes.get(oid); + + if (value instanceof Vector) + { + return (Attribute)((Vector)value).elementAt(0); + } + + return (Attribute)value; + } + + /** + * Return all the attributes matching the OBJECT IDENTIFIER oid. The vector will be + * empty if there are no attributes of the required type present. + * + * @param oid type of attribute required. + * @return a vector of all the attributes found of type oid. + */ + public ASN1EncodableVector getAll( + DERObjectIdentifier oid) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + Object value = attributes.get(oid); + + if (value instanceof Vector) + { + Enumeration e = ((Vector)value).elements(); + + while (e.hasMoreElements()) + { + v.add((Attribute)e.nextElement()); + } + } + else if (value != null) + { + v.add((Attribute)value); + } + + return v; + } + + public Hashtable toHashtable() + { + return copyTable(attributes); + } + + public ASN1EncodableVector toASN1EncodableVector() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + Enumeration e = attributes.elements(); + + while (e.hasMoreElements()) + { + Object value = e.nextElement(); + + if (value instanceof Vector) + { + Enumeration en = ((Vector)value).elements(); + + while (en.hasMoreElements()) + { + v.add(Attribute.getInstance(en.nextElement())); + } + } + else + { + v.add(Attribute.getInstance(value)); + } + } + + return v; + } + + private Hashtable copyTable( + Hashtable in) + { + Hashtable out = new Hashtable(); + Enumeration e = in.keys(); + + while (e.hasMoreElements()) + { + Object key = e.nextElement(); + + out.put(key, in.get(key)); + } + + return out; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/AuthEnvelopedData.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/AuthEnvelopedData.java new file mode 100644 index 000000000..7fb14f085 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/AuthEnvelopedData.java @@ -0,0 +1,218 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.BERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class AuthEnvelopedData + extends ASN1Encodable +{ + private DERInteger version; + private OriginatorInfo originatorInfo; + private ASN1Set recipientInfos; + private EncryptedContentInfo authEncryptedContentInfo; + private ASN1Set authAttrs; + private ASN1OctetString mac; + private ASN1Set unauthAttrs; + + public AuthEnvelopedData( + OriginatorInfo originatorInfo, + ASN1Set recipientInfos, + EncryptedContentInfo authEncryptedContentInfo, + ASN1Set authAttrs, + ASN1OctetString mac, + ASN1Set unauthAttrs) + { + // "It MUST be set to 0." + this.version = new DERInteger(0); + + this.originatorInfo = originatorInfo; + + // TODO + // "There MUST be at least one element in the collection." + this.recipientInfos = recipientInfos; + + this.authEncryptedContentInfo = authEncryptedContentInfo; + + // TODO + // "The authAttrs MUST be present if the content type carried in + // EncryptedContentInfo is not id-data." + this.authAttrs = authAttrs; + + this.mac = mac; + + this.unauthAttrs = unauthAttrs; + } + + public AuthEnvelopedData( + ASN1Sequence seq) + { + int index = 0; + + // TODO + // "It MUST be set to 0." + DERObject tmp = seq.getObjectAt(index++).getDERObject(); + version = (DERInteger)tmp; + + tmp = seq.getObjectAt(index++).getDERObject(); + if (tmp instanceof ASN1TaggedObject) + { + originatorInfo = OriginatorInfo.getInstance((ASN1TaggedObject)tmp, false); + tmp = seq.getObjectAt(index++).getDERObject(); + } + + // TODO + // "There MUST be at least one element in the collection." + recipientInfos = ASN1Set.getInstance(tmp); + + tmp = seq.getObjectAt(index++).getDERObject(); + authEncryptedContentInfo = EncryptedContentInfo.getInstance(tmp); + + tmp = seq.getObjectAt(index++).getDERObject(); + if (tmp instanceof ASN1TaggedObject) + { + authAttrs = ASN1Set.getInstance((ASN1TaggedObject)tmp, false); + tmp = seq.getObjectAt(index++).getDERObject(); + } + else + { + // TODO + // "The authAttrs MUST be present if the content type carried in + // EncryptedContentInfo is not id-data." + } + + mac = ASN1OctetString.getInstance(tmp); + + if (seq.size() > index) + { + tmp = seq.getObjectAt(index++).getDERObject(); + unauthAttrs = ASN1Set.getInstance((ASN1TaggedObject)tmp, false); + } + } + + /** + * return an AuthEnvelopedData object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @throws IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static AuthEnvelopedData getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * return an AuthEnvelopedData object from the given object. + * + * @param obj the object we want converted. + * @throws IllegalArgumentException if the object cannot be converted. + */ + public static AuthEnvelopedData getInstance( + Object obj) + { + if (obj == null || obj instanceof AuthEnvelopedData) + { + return (AuthEnvelopedData)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new AuthEnvelopedData((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid AuthEnvelopedData: " + obj.getClass().getName()); + } + + public DERInteger getVersion() + { + return version; + } + + public OriginatorInfo getOriginatorInfo() + { + return originatorInfo; + } + + public ASN1Set getRecipientInfos() + { + return recipientInfos; + } + + public EncryptedContentInfo getAuthEncryptedContentInfo() + { + return authEncryptedContentInfo; + } + + public ASN1Set getAuthAttrs() + { + return authAttrs; + } + + public ASN1OctetString getMac() + { + return mac; + } + + public ASN1Set getUnauthAttrs() + { + return unauthAttrs; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * AuthEnvelopedData ::= SEQUENCE {
+     *   version CMSVersion,
+     *   originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
+     *   recipientInfos RecipientInfos,
+     *   authEncryptedContentInfo EncryptedContentInfo,
+     *   authAttrs [1] IMPLICIT AuthAttributes OPTIONAL,
+     *   mac MessageAuthenticationCode,
+     *   unauthAttrs [2] IMPLICIT UnauthAttributes OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + + if (originatorInfo != null) + { + v.add(new DERTaggedObject(false, 0, originatorInfo)); + } + + v.add(recipientInfos); + v.add(authEncryptedContentInfo); + + // "authAttrs optionally contains the authenticated attributes." + if (authAttrs != null) + { + // "AuthAttributes MUST be DER encoded, even if the rest of the + // AuthEnvelopedData structure is BER encoded." + v.add(new DERTaggedObject(false, 1, authAttrs)); + } + + v.add(mac); + + // "unauthAttrs optionally contains the unauthenticated attributes." + if (unauthAttrs != null) + { + v.add(new DERTaggedObject(false, 2, unauthAttrs)); + } + + return new BERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/AuthEnvelopedDataParser.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/AuthEnvelopedDataParser.java new file mode 100644 index 000000000..99ed4c1e9 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/AuthEnvelopedDataParser.java @@ -0,0 +1,157 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import java.io.IOException; + +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1SequenceParser; +import com.google.bitcoin.bouncycastle.asn1.ASN1SetParser; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObjectParser; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERTags; + +/** + * Produce an object suitable for an ASN1OutputStream. + * + *
+ * AuthEnvelopedData ::= SEQUENCE {
+ *   version CMSVersion,
+ *   originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
+ *   recipientInfos RecipientInfos,
+ *   authEncryptedContentInfo EncryptedContentInfo,
+ *   authAttrs [1] IMPLICIT AuthAttributes OPTIONAL,
+ *   mac MessageAuthenticationCode,
+ *   unauthAttrs [2] IMPLICIT UnauthAttributes OPTIONAL }
+ * 
+ */ +public class AuthEnvelopedDataParser +{ + private ASN1SequenceParser seq; + private DERInteger version; + private DEREncodable nextObject; + private boolean originatorInfoCalled; + + public AuthEnvelopedDataParser(ASN1SequenceParser seq) throws IOException + { + this.seq = seq; + + // TODO + // "It MUST be set to 0." + this.version = (DERInteger)seq.readObject(); + } + + public DERInteger getVersion() + { + return version; + } + + public OriginatorInfo getOriginatorInfo() + throws IOException + { + originatorInfoCalled = true; + + if (nextObject == null) + { + nextObject = seq.readObject(); + } + + if (nextObject instanceof ASN1TaggedObjectParser && ((ASN1TaggedObjectParser)nextObject).getTagNo() == 0) + { + ASN1SequenceParser originatorInfo = (ASN1SequenceParser) ((ASN1TaggedObjectParser)nextObject).getObjectParser(DERTags.SEQUENCE, false); + nextObject = null; + return OriginatorInfo.getInstance(originatorInfo.getDERObject()); + } + + return null; + } + + public ASN1SetParser getRecipientInfos() + throws IOException + { + if (!originatorInfoCalled) + { + getOriginatorInfo(); + } + + if (nextObject == null) + { + nextObject = seq.readObject(); + } + + ASN1SetParser recipientInfos = (ASN1SetParser)nextObject; + nextObject = null; + return recipientInfos; + } + + public EncryptedContentInfoParser getAuthEncryptedContentInfo() + throws IOException + { + if (nextObject == null) + { + nextObject = seq.readObject(); + } + + if (nextObject != null) + { + ASN1SequenceParser o = (ASN1SequenceParser) nextObject; + nextObject = null; + return new EncryptedContentInfoParser(o); + } + + return null; + } + + public ASN1SetParser getAuthAttrs() + throws IOException + { + if (nextObject == null) + { + nextObject = seq.readObject(); + } + + if (nextObject instanceof ASN1TaggedObjectParser) + { + DEREncodable o = nextObject; + nextObject = null; + return (ASN1SetParser)((ASN1TaggedObjectParser)o).getObjectParser(DERTags.SET, false); + } + + // TODO + // "The authAttrs MUST be present if the content type carried in + // EncryptedContentInfo is not id-data." + + return null; + } + + public ASN1OctetString getMac() + throws IOException + { + if (nextObject == null) + { + nextObject = seq.readObject(); + } + + DEREncodable o = nextObject; + nextObject = null; + + return ASN1OctetString.getInstance(o.getDERObject()); + } + + public ASN1SetParser getUnauthAttrs() + throws IOException + { + if (nextObject == null) + { + nextObject = seq.readObject(); + } + + if (nextObject != null) + { + DEREncodable o = nextObject; + nextObject = null; + return (ASN1SetParser)((ASN1TaggedObjectParser)o).getObjectParser(DERTags.SET, false); + } + + return null; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/AuthenticatedData.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/AuthenticatedData.java new file mode 100644 index 000000000..4a1b2ddf8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/AuthenticatedData.java @@ -0,0 +1,288 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.BERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class AuthenticatedData + extends ASN1Encodable +{ + private DERInteger version; + private OriginatorInfo originatorInfo; + private ASN1Set recipientInfos; + private AlgorithmIdentifier macAlgorithm; + private AlgorithmIdentifier digestAlgorithm; + private ContentInfo encapsulatedContentInfo; + private ASN1Set authAttrs; + private ASN1OctetString mac; + private ASN1Set unauthAttrs; + + public AuthenticatedData( + OriginatorInfo originatorInfo, + ASN1Set recipientInfos, + AlgorithmIdentifier macAlgorithm, + AlgorithmIdentifier digestAlgorithm, + ContentInfo encapsulatedContent, + ASN1Set authAttrs, + ASN1OctetString mac, + ASN1Set unauthAttrs) + { + if (digestAlgorithm != null || authAttrs != null) + { + if (digestAlgorithm == null || authAttrs == null) + { + throw new IllegalArgumentException("digestAlgorithm and authAttrs must be set together"); + } + } + + version = new DERInteger(calculateVersion(originatorInfo)); + + this.originatorInfo = originatorInfo; + this.macAlgorithm = macAlgorithm; + this.digestAlgorithm = digestAlgorithm; + this.recipientInfos = recipientInfos; + this.encapsulatedContentInfo = encapsulatedContent; + this.authAttrs = authAttrs; + this.mac = mac; + this.unauthAttrs = unauthAttrs; + } + + public AuthenticatedData( + ASN1Sequence seq) + { + int index = 0; + + version = (DERInteger)seq.getObjectAt(index++); + + Object tmp = seq.getObjectAt(index++); + + if (tmp instanceof ASN1TaggedObject) + { + originatorInfo = OriginatorInfo.getInstance((ASN1TaggedObject)tmp, false); + tmp = seq.getObjectAt(index++); + } + + recipientInfos = ASN1Set.getInstance(tmp); + macAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(index++)); + + tmp = seq.getObjectAt(index++); + + if (tmp instanceof ASN1TaggedObject) + { + digestAlgorithm = AlgorithmIdentifier.getInstance((ASN1TaggedObject)tmp, false); + tmp = seq.getObjectAt(index++); + } + + encapsulatedContentInfo = ContentInfo.getInstance(tmp); + + tmp = seq.getObjectAt(index++); + + if (tmp instanceof ASN1TaggedObject) + { + authAttrs = ASN1Set.getInstance((ASN1TaggedObject)tmp, false); + tmp = seq.getObjectAt(index++); + } + + mac = ASN1OctetString.getInstance(tmp); + + if (seq.size() > index) + { + unauthAttrs = ASN1Set.getInstance((ASN1TaggedObject)seq.getObjectAt(index), false); + } + } + + /** + * return an AuthenticatedData object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @throws IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static AuthenticatedData getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * return an AuthenticatedData object from the given object. + * + * @param obj the object we want converted. + * @throws IllegalArgumentException if the object cannot be converted. + */ + public static AuthenticatedData getInstance( + Object obj) + { + if (obj == null || obj instanceof AuthenticatedData) + { + return (AuthenticatedData)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new AuthenticatedData((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid AuthenticatedData: " + obj.getClass().getName()); + } + + public DERInteger getVersion() + { + return version; + } + + public OriginatorInfo getOriginatorInfo() + { + return originatorInfo; + } + + public ASN1Set getRecipientInfos() + { + return recipientInfos; + } + + public AlgorithmIdentifier getMacAlgorithm() + { + return macAlgorithm; + } + + public ContentInfo getEncapsulatedContentInfo() + { + return encapsulatedContentInfo; + } + + public ASN1Set getAuthAttrs() + { + return authAttrs; + } + + public ASN1OctetString getMac() + { + return mac; + } + + public ASN1Set getUnauthAttrs() + { + return unauthAttrs; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * AuthenticatedData ::= SEQUENCE {
+     *       version CMSVersion,
+     *       originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
+     *       recipientInfos RecipientInfos,
+     *       macAlgorithm MessageAuthenticationCodeAlgorithm,
+     *       digestAlgorithm [1] DigestAlgorithmIdentifier OPTIONAL,
+     *       encapContentInfo EncapsulatedContentInfo,
+     *       authAttrs [2] IMPLICIT AuthAttributes OPTIONAL,
+     *       mac MessageAuthenticationCode,
+     *       unauthAttrs [3] IMPLICIT UnauthAttributes OPTIONAL }
+     *
+     * AuthAttributes ::= SET SIZE (1..MAX) OF Attribute
+     *
+     * UnauthAttributes ::= SET SIZE (1..MAX) OF Attribute
+     *
+     * MessageAuthenticationCode ::= OCTET STRING
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + + if (originatorInfo != null) + { + v.add(new DERTaggedObject(false, 0, originatorInfo)); + } + + v.add(recipientInfos); + v.add(macAlgorithm); + + if (digestAlgorithm != null) + { + v.add(new DERTaggedObject(false, 1, digestAlgorithm)); + } + + v.add(encapsulatedContentInfo); + + if (authAttrs != null) + { + v.add(new DERTaggedObject(false, 2, authAttrs)); + } + + v.add(mac); + + if (unauthAttrs != null) + { + v.add(new DERTaggedObject(false, 3, unauthAttrs)); + } + + return new BERSequence(v); + } + + public static int calculateVersion(OriginatorInfo origInfo) + { + if (origInfo == null) + { + return 0; + } + else + { + int ver = 0; + + for (Enumeration e = origInfo.getCertificates().getObjects(); e.hasMoreElements();) + { + Object obj = e.nextElement(); + + if (obj instanceof ASN1TaggedObject) + { + ASN1TaggedObject tag = (ASN1TaggedObject)obj; + + if (tag.getTagNo() == 2) + { + ver = 1; + } + else if (tag.getTagNo() == 3) + { + ver = 3; + break; + } + } + } + + for (Enumeration e = origInfo.getCRLs().getObjects(); e.hasMoreElements();) + { + Object obj = e.nextElement(); + + if (obj instanceof ASN1TaggedObject) + { + ASN1TaggedObject tag = (ASN1TaggedObject)obj; + + if (tag.getTagNo() == 1) + { + ver = 3; + break; + } + } + } + + return ver; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/AuthenticatedDataParser.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/AuthenticatedDataParser.java new file mode 100644 index 000000000..6a45f7603 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/AuthenticatedDataParser.java @@ -0,0 +1,178 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1SequenceParser; +import com.google.bitcoin.bouncycastle.asn1.ASN1SetParser; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObjectParser; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERTags; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +import java.io.IOException; + +/** + * Produce an object suitable for an ASN1OutputStream. + *
+ * AuthenticatedData ::= SEQUENCE {
+ *       version CMSVersion,
+ *       originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
+ *       recipientInfos RecipientInfos,
+ *       macAlgorithm MessageAuthenticationCodeAlgorithm,
+ *       digestAlgorithm [1] DigestAlgorithmIdentifier OPTIONAL,
+ *       encapContentInfo EncapsulatedContentInfo,
+ *       authAttrs [2] IMPLICIT AuthAttributes OPTIONAL,
+ *       mac MessageAuthenticationCode,
+ *       unauthAttrs [3] IMPLICIT UnauthAttributes OPTIONAL }
+ *
+ * AuthAttributes ::= SET SIZE (1..MAX) OF Attribute
+ *
+ * UnauthAttributes ::= SET SIZE (1..MAX) OF Attribute
+ *
+ * MessageAuthenticationCode ::= OCTET STRING
+ * 
+ */ +public class AuthenticatedDataParser +{ + private ASN1SequenceParser seq; + private DERInteger version; + private DEREncodable nextObject; + private boolean originatorInfoCalled; + + public AuthenticatedDataParser( + ASN1SequenceParser seq) + throws IOException + { + this.seq = seq; + this.version = (DERInteger)seq.readObject(); + } + + public DERInteger getVersion() + { + return version; + } + + public OriginatorInfo getOriginatorInfo() + throws IOException + { + originatorInfoCalled = true; + + if (nextObject == null) + { + nextObject = seq.readObject(); + } + + if (nextObject instanceof ASN1TaggedObjectParser && ((ASN1TaggedObjectParser)nextObject).getTagNo() == 0) + { + ASN1SequenceParser originatorInfo = (ASN1SequenceParser) ((ASN1TaggedObjectParser)nextObject).getObjectParser(DERTags.SEQUENCE, false); + nextObject = null; + return OriginatorInfo.getInstance(originatorInfo.getDERObject()); + } + + return null; + } + + public ASN1SetParser getRecipientInfos() + throws IOException + { + if (!originatorInfoCalled) + { + getOriginatorInfo(); + } + + if (nextObject == null) + { + nextObject = seq.readObject(); + } + + ASN1SetParser recipientInfos = (ASN1SetParser)nextObject; + nextObject = null; + return recipientInfos; + } + + public AlgorithmIdentifier getMacAlgorithm() + throws IOException + { + if (nextObject == null) + { + nextObject = seq.readObject(); + } + + if (nextObject != null) + { + ASN1SequenceParser o = (ASN1SequenceParser)nextObject; + nextObject = null; + return AlgorithmIdentifier.getInstance(o.getDERObject()); + } + + return null; + } + + public ContentInfoParser getEnapsulatedContentInfo() + throws IOException + { + if (nextObject == null) + { + nextObject = seq.readObject(); + } + + if (nextObject != null) + { + ASN1SequenceParser o = (ASN1SequenceParser)nextObject; + nextObject = null; + return new ContentInfoParser(o); + } + + return null; + } + + public ASN1SetParser getAuthAttrs() + throws IOException + { + if (nextObject == null) + { + nextObject = seq.readObject(); + } + + if (nextObject instanceof ASN1TaggedObjectParser) + { + DEREncodable o = nextObject; + nextObject = null; + return (ASN1SetParser)((ASN1TaggedObjectParser)o).getObjectParser(DERTags.SET, false); + } + + return null; + } + + public ASN1OctetString getMac() + throws IOException + { + if (nextObject == null) + { + nextObject = seq.readObject(); + } + + DEREncodable o = nextObject; + nextObject = null; + + return ASN1OctetString.getInstance(o.getDERObject()); + } + + public ASN1SetParser getUnauthAttrs() + throws IOException + { + if (nextObject == null) + { + nextObject = seq.readObject(); + } + + if (nextObject != null) + { + DEREncodable o = nextObject; + nextObject = null; + return (ASN1SetParser)((ASN1TaggedObjectParser)o).getObjectParser(DERTags.SET, false); + } + + return null; + } +} \ No newline at end of file diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/CMSAttributes.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/CMSAttributes.java new file mode 100644 index 000000000..0536f2981 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/CMSAttributes.java @@ -0,0 +1,13 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; + +public interface CMSAttributes +{ + public static final DERObjectIdentifier contentType = PKCSObjectIdentifiers.pkcs_9_at_contentType; + public static final DERObjectIdentifier messageDigest = PKCSObjectIdentifiers.pkcs_9_at_messageDigest; + public static final DERObjectIdentifier signingTime = PKCSObjectIdentifiers.pkcs_9_at_signingTime; + public static final DERObjectIdentifier counterSignature = PKCSObjectIdentifiers.pkcs_9_at_counterSignature; + public static final DERObjectIdentifier contentHint = PKCSObjectIdentifiers.id_aa_contentHint; +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/CMSObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/CMSObjectIdentifiers.java new file mode 100644 index 000000000..c1a27f584 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/CMSObjectIdentifiers.java @@ -0,0 +1,17 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; + +public interface CMSObjectIdentifiers +{ + static final DERObjectIdentifier data = PKCSObjectIdentifiers.data; + static final DERObjectIdentifier signedData = PKCSObjectIdentifiers.signedData; + static final DERObjectIdentifier envelopedData = PKCSObjectIdentifiers.envelopedData; + static final DERObjectIdentifier signedAndEnvelopedData = PKCSObjectIdentifiers.signedAndEnvelopedData; + static final DERObjectIdentifier digestedData = PKCSObjectIdentifiers.digestedData; + static final DERObjectIdentifier encryptedData = PKCSObjectIdentifiers.encryptedData; + static final DERObjectIdentifier authenticatedData = PKCSObjectIdentifiers.id_ct_authData; + static final DERObjectIdentifier compressedData = PKCSObjectIdentifiers.id_ct_compressedData; + static final DERObjectIdentifier authEnvelopedData = PKCSObjectIdentifiers.id_ct_authEnvelopedData; +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/CompressedData.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/CompressedData.java new file mode 100644 index 000000000..46c89c5c5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/CompressedData.java @@ -0,0 +1,110 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.BERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +/** + * RFC 3274 - CMS Compressed Data. + *
+ * CompressedData ::= SEQUENCE {
+ *  version CMSVersion,
+ *  compressionAlgorithm CompressionAlgorithmIdentifier,
+ *  encapContentInfo EncapsulatedContentInfo
+ * }
+ * 
+ */ +public class CompressedData + extends ASN1Encodable +{ + private DERInteger version; + private AlgorithmIdentifier compressionAlgorithm; + private ContentInfo encapContentInfo; + + public CompressedData( + AlgorithmIdentifier compressionAlgorithm, + ContentInfo encapContentInfo) + { + this.version = new DERInteger(0); + this.compressionAlgorithm = compressionAlgorithm; + this.encapContentInfo = encapContentInfo; + } + + public CompressedData( + ASN1Sequence seq) + { + this.version = (DERInteger)seq.getObjectAt(0); + this.compressionAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(1)); + this.encapContentInfo = ContentInfo.getInstance(seq.getObjectAt(2)); + + } + + /** + * return a CompressedData object from a tagged object. + * + * @param _ato the tagged object holding the object we want. + * @param _explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static CompressedData getInstance( + ASN1TaggedObject _ato, + boolean _explicit) + { + return getInstance(ASN1Sequence.getInstance(_ato, _explicit)); + } + + /** + * return a CompressedData object from the given object. + * + * @param _obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static CompressedData getInstance( + Object _obj) + { + if (_obj == null || _obj instanceof CompressedData) + { + return (CompressedData)_obj; + } + + if (_obj instanceof ASN1Sequence) + { + return new CompressedData((ASN1Sequence)_obj); + } + + throw new IllegalArgumentException("Invalid CompressedData: " + _obj.getClass().getName()); + } + + public DERInteger getVersion() + { + return version; + } + + public AlgorithmIdentifier getCompressionAlgorithmIdentifier() + { + return compressionAlgorithm; + } + + public ContentInfo getEncapContentInfo() + { + return encapContentInfo; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(compressionAlgorithm); + v.add(encapContentInfo); + + return new BERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/CompressedDataParser.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/CompressedDataParser.java new file mode 100644 index 000000000..c5d69f1fb --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/CompressedDataParser.java @@ -0,0 +1,48 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1SequenceParser; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +import java.io.IOException; + +/** + * RFC 3274 - CMS Compressed Data. + *
+ * CompressedData ::= SEQUENCE {
+ *  version CMSVersion,
+ *  compressionAlgorithm CompressionAlgorithmIdentifier,
+ *  encapContentInfo EncapsulatedContentInfo
+ * }
+ * 
+ */ +public class CompressedDataParser +{ + private DERInteger _version; + private AlgorithmIdentifier _compressionAlgorithm; + private ContentInfoParser _encapContentInfo; + + public CompressedDataParser( + ASN1SequenceParser seq) + throws IOException + { + this._version = (DERInteger)seq.readObject(); + this._compressionAlgorithm = AlgorithmIdentifier.getInstance(seq.readObject().getDERObject()); + this._encapContentInfo = new ContentInfoParser((ASN1SequenceParser)seq.readObject()); + } + + public DERInteger getVersion() + { + return _version; + } + + public AlgorithmIdentifier getCompressionAlgorithmIdentifier() + { + return _compressionAlgorithm; + } + + public ContentInfoParser getEncapContentInfo() + { + return _encapContentInfo; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/ContentInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/ContentInfo.java new file mode 100644 index 000000000..9b9ecd99d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/ContentInfo.java @@ -0,0 +1,90 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.BERSequence; +import com.google.bitcoin.bouncycastle.asn1.BERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public class ContentInfo + extends ASN1Encodable + implements CMSObjectIdentifiers +{ + private DERObjectIdentifier contentType; + private DEREncodable content; + + public static ContentInfo getInstance( + Object obj) + { + if (obj == null || obj instanceof ContentInfo) + { + return (ContentInfo)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new ContentInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public ContentInfo( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + contentType = (DERObjectIdentifier)e.nextElement(); + + if (e.hasMoreElements()) + { + content = ((ASN1TaggedObject)e.nextElement()).getObject(); + } + } + + public ContentInfo( + DERObjectIdentifier contentType, + DEREncodable content) + { + this.contentType = contentType; + this.content = content; + } + + public DERObjectIdentifier getContentType() + { + return contentType; + } + + public DEREncodable getContent() + { + return content; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * ContentInfo ::= SEQUENCE {
+     *          contentType ContentType,
+     *          content
+     *          [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(contentType); + + if (content != null) + { + v.add(new BERTaggedObject(0, content)); + } + + return new BERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/ContentInfoParser.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/ContentInfoParser.java new file mode 100644 index 000000000..4bd7b6fcb --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/ContentInfoParser.java @@ -0,0 +1,48 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1SequenceParser; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObjectParser; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +import java.io.IOException; + +/** + * Produce an object suitable for an ASN1OutputStream. + *
+ * ContentInfo ::= SEQUENCE {
+ *          contentType ContentType,
+ *          content
+ *          [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
+ * 
+ */ +public class ContentInfoParser +{ + private DERObjectIdentifier contentType; + private ASN1TaggedObjectParser content; + + public ContentInfoParser( + ASN1SequenceParser seq) + throws IOException + { + contentType = (DERObjectIdentifier)seq.readObject(); + content = (ASN1TaggedObjectParser)seq.readObject(); + } + + public DERObjectIdentifier getContentType() + { + return contentType; + } + + public DEREncodable getContent( + int tag) + throws IOException + { + if (content != null) + { + return content.getObjectParser(tag, true); + } + + return null; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/EncryptedContentInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/EncryptedContentInfo.java new file mode 100644 index 000000000..cbcf6976b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/EncryptedContentInfo.java @@ -0,0 +1,106 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.BERSequence; +import com.google.bitcoin.bouncycastle.asn1.BERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class EncryptedContentInfo + extends ASN1Encodable +{ + private DERObjectIdentifier contentType; + private AlgorithmIdentifier contentEncryptionAlgorithm; + private ASN1OctetString encryptedContent; + + public EncryptedContentInfo( + DERObjectIdentifier contentType, + AlgorithmIdentifier contentEncryptionAlgorithm, + ASN1OctetString encryptedContent) + { + this.contentType = contentType; + this.contentEncryptionAlgorithm = contentEncryptionAlgorithm; + this.encryptedContent = encryptedContent; + } + + public EncryptedContentInfo( + ASN1Sequence seq) + { + contentType = (DERObjectIdentifier)seq.getObjectAt(0); + contentEncryptionAlgorithm = AlgorithmIdentifier.getInstance( + seq.getObjectAt(1)); + if (seq.size() > 2) + { + encryptedContent = ASN1OctetString.getInstance( + (ASN1TaggedObject)seq.getObjectAt(2), false); + } + } + + /** + * return an EncryptedContentInfo object from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static EncryptedContentInfo getInstance( + Object obj) + { + if (obj == null || obj instanceof EncryptedContentInfo) + { + return (EncryptedContentInfo)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new EncryptedContentInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid EncryptedContentInfo: " + + obj.getClass().getName()); + } + + public DERObjectIdentifier getContentType() + { + return contentType; + } + + public AlgorithmIdentifier getContentEncryptionAlgorithm() + { + return contentEncryptionAlgorithm; + } + + public ASN1OctetString getEncryptedContent() + { + return encryptedContent; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * EncryptedContentInfo ::= SEQUENCE {
+     *     contentType ContentType,
+     *     contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
+     *     encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL 
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(contentType); + v.add(contentEncryptionAlgorithm); + + if (encryptedContent != null) + { + v.add(new BERTaggedObject(false, 0, encryptedContent)); + } + + return new BERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/EncryptedContentInfoParser.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/EncryptedContentInfoParser.java new file mode 100644 index 000000000..0138de86e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/EncryptedContentInfoParser.java @@ -0,0 +1,51 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import java.io.IOException; + +import com.google.bitcoin.bouncycastle.asn1.ASN1SequenceParser; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObjectParser; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +/** + *
+ * EncryptedContentInfo ::= SEQUENCE {
+ *     contentType ContentType,
+ *     contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
+ *     encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL 
+ * }
+ * 
+ */ +public class EncryptedContentInfoParser +{ + private DERObjectIdentifier _contentType; + private AlgorithmIdentifier _contentEncryptionAlgorithm; + private ASN1TaggedObjectParser _encryptedContent; + + public EncryptedContentInfoParser( + ASN1SequenceParser seq) + throws IOException + { + _contentType = (DERObjectIdentifier)seq.readObject(); + _contentEncryptionAlgorithm = AlgorithmIdentifier.getInstance(seq.readObject().getDERObject()); + _encryptedContent = (ASN1TaggedObjectParser)seq.readObject(); + } + + public DERObjectIdentifier getContentType() + { + return _contentType; + } + + public AlgorithmIdentifier getContentEncryptionAlgorithm() + { + return _contentEncryptionAlgorithm; + } + + public DEREncodable getEncryptedContent( + int tag) + throws IOException + { + return _encryptedContent.getObjectParser(tag, false); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/EncryptedData.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/EncryptedData.java new file mode 100644 index 000000000..1462086bc --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/EncryptedData.java @@ -0,0 +1,94 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.BERSequence; +import com.google.bitcoin.bouncycastle.asn1.BERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class EncryptedData + extends ASN1Encodable +{ + private DERInteger version; + private EncryptedContentInfo encryptedContentInfo; + private ASN1Set unprotectedAttrs; + + public static EncryptedData getInstance(Object o) + { + if (o instanceof EncryptedData) + { + return (EncryptedData)o; + } + + if (o instanceof ASN1Sequence) + { + return new EncryptedData((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid EncryptedData: " + o.getClass().getName()); + } + + public EncryptedData(EncryptedContentInfo encInfo) + { + this(encInfo, null); + } + + public EncryptedData(EncryptedContentInfo encInfo, ASN1Set unprotectedAttrs) + { + this.version = new DERInteger((unprotectedAttrs == null) ? 0 : 2); + this.encryptedContentInfo = encInfo; + this.unprotectedAttrs = unprotectedAttrs; + } + + private EncryptedData(ASN1Sequence seq) + { + this.version = DERInteger.getInstance(seq.getObjectAt(0)); + this.encryptedContentInfo = EncryptedContentInfo.getInstance(seq.getObjectAt(1)); + + if (seq.size() == 3) + { + this.unprotectedAttrs = ASN1Set.getInstance(seq.getObjectAt(2)); + } + } + + public DERInteger getVersion() + { + return version; + } + + public EncryptedContentInfo getEncryptedContentInfo() + { + return encryptedContentInfo; + } + + public ASN1Set getUnprotectedAttrs() + { + return unprotectedAttrs; + } + + /** + *
+     *       EncryptedData ::= SEQUENCE {
+     *                     version CMSVersion,
+     *                     encryptedContentInfo EncryptedContentInfo,
+     *                     unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(encryptedContentInfo); + if (unprotectedAttrs != null) + { + v.add(new BERTaggedObject(false, 1, unprotectedAttrs)); + } + + return new BERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/EnvelopedData.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/EnvelopedData.java new file mode 100644 index 000000000..7e80c47f3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/EnvelopedData.java @@ -0,0 +1,179 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.BERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class EnvelopedData + extends ASN1Encodable +{ + private DERInteger version; + private OriginatorInfo originatorInfo; + private ASN1Set recipientInfos; + private EncryptedContentInfo encryptedContentInfo; + private ASN1Set unprotectedAttrs; + + public EnvelopedData( + OriginatorInfo originatorInfo, + ASN1Set recipientInfos, + EncryptedContentInfo encryptedContentInfo, + ASN1Set unprotectedAttrs) + { + if (originatorInfo != null || unprotectedAttrs != null) + { + version = new DERInteger(2); + } + else + { + version = new DERInteger(0); + + Enumeration e = recipientInfos.getObjects(); + + while (e.hasMoreElements()) + { + RecipientInfo ri = RecipientInfo.getInstance(e.nextElement()); + + if (!ri.getVersion().equals(version)) + { + version = new DERInteger(2); + break; + } + } + } + + this.originatorInfo = originatorInfo; + this.recipientInfos = recipientInfos; + this.encryptedContentInfo = encryptedContentInfo; + this.unprotectedAttrs = unprotectedAttrs; + } + + public EnvelopedData( + ASN1Sequence seq) + { + int index = 0; + + version = (DERInteger)seq.getObjectAt(index++); + + Object tmp = seq.getObjectAt(index++); + + if (tmp instanceof ASN1TaggedObject) + { + originatorInfo = OriginatorInfo.getInstance((ASN1TaggedObject)tmp, false); + tmp = seq.getObjectAt(index++); + } + + recipientInfos = ASN1Set.getInstance(tmp); + + encryptedContentInfo = EncryptedContentInfo.getInstance(seq.getObjectAt(index++)); + + if(seq.size() > index) + { + unprotectedAttrs = ASN1Set.getInstance((ASN1TaggedObject)seq.getObjectAt(index), false); + } + } + + /** + * return an EnvelopedData object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static EnvelopedData getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * return an EnvelopedData object from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static EnvelopedData getInstance( + Object obj) + { + if (obj == null || obj instanceof EnvelopedData) + { + return (EnvelopedData)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new EnvelopedData((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid EnvelopedData: " + obj.getClass().getName()); + } + + public DERInteger getVersion() + { + return version; + } + + public OriginatorInfo getOriginatorInfo() + { + return originatorInfo; + } + + public ASN1Set getRecipientInfos() + { + return recipientInfos; + } + + public EncryptedContentInfo getEncryptedContentInfo() + { + return encryptedContentInfo; + } + + public ASN1Set getUnprotectedAttrs() + { + return unprotectedAttrs; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * EnvelopedData ::= SEQUENCE {
+     *     version CMSVersion,
+     *     originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
+     *     recipientInfos RecipientInfos,
+     *     encryptedContentInfo EncryptedContentInfo,
+     *     unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL 
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + + if (originatorInfo != null) + { + v.add(new DERTaggedObject(false, 0, originatorInfo)); + } + + v.add(recipientInfos); + v.add(encryptedContentInfo); + + if (unprotectedAttrs != null) + { + v.add(new DERTaggedObject(false, 1, unprotectedAttrs)); + } + + return new BERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/EnvelopedDataParser.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/EnvelopedDataParser.java new file mode 100644 index 000000000..15878f4ff --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/EnvelopedDataParser.java @@ -0,0 +1,118 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1SequenceParser; +import com.google.bitcoin.bouncycastle.asn1.ASN1SetParser; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObjectParser; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERTags; + +import java.io.IOException; + +/** + *
+ * EnvelopedData ::= SEQUENCE {
+ *     version CMSVersion,
+ *     originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
+ *     recipientInfos RecipientInfos,
+ *     encryptedContentInfo EncryptedContentInfo,
+ *     unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL 
+ * }
+ * 
+ */ +public class EnvelopedDataParser +{ + private ASN1SequenceParser _seq; + private DERInteger _version; + private DEREncodable _nextObject; + private boolean _originatorInfoCalled; + + public EnvelopedDataParser( + ASN1SequenceParser seq) + throws IOException + { + this._seq = seq; + this._version = (DERInteger)seq.readObject(); + } + + public DERInteger getVersion() + { + return _version; + } + + public OriginatorInfo getOriginatorInfo() + throws IOException + { + _originatorInfoCalled = true; + + if (_nextObject == null) + { + _nextObject = _seq.readObject(); + } + + if (_nextObject instanceof ASN1TaggedObjectParser && ((ASN1TaggedObjectParser)_nextObject).getTagNo() == 0) + { + ASN1SequenceParser originatorInfo = (ASN1SequenceParser) ((ASN1TaggedObjectParser)_nextObject).getObjectParser(DERTags.SEQUENCE, false); + _nextObject = null; + return OriginatorInfo.getInstance(originatorInfo.getDERObject()); + } + + return null; + } + + public ASN1SetParser getRecipientInfos() + throws IOException + { + if (!_originatorInfoCalled) + { + getOriginatorInfo(); + } + + if (_nextObject == null) + { + _nextObject = _seq.readObject(); + } + + ASN1SetParser recipientInfos = (ASN1SetParser)_nextObject; + _nextObject = null; + return recipientInfos; + } + + public EncryptedContentInfoParser getEncryptedContentInfo() + throws IOException + { + if (_nextObject == null) + { + _nextObject = _seq.readObject(); + } + + + if (_nextObject != null) + { + ASN1SequenceParser o = (ASN1SequenceParser) _nextObject; + _nextObject = null; + return new EncryptedContentInfoParser(o); + } + + return null; + } + + public ASN1SetParser getUnprotectedAttrs() + throws IOException + { + if (_nextObject == null) + { + _nextObject = _seq.readObject(); + } + + + if (_nextObject != null) + { + DEREncodable o = _nextObject; + _nextObject = null; + return (ASN1SetParser)((ASN1TaggedObjectParser)o).getObjectParser(DERTags.SET, false); + } + + return null; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/IssuerAndSerialNumber.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/IssuerAndSerialNumber.java new file mode 100644 index 000000000..143bc9ddd --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/IssuerAndSerialNumber.java @@ -0,0 +1,77 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Name; + +public class IssuerAndSerialNumber + extends ASN1Encodable +{ + X509Name name; + DERInteger serialNumber; + + public static IssuerAndSerialNumber getInstance( + Object obj) + { + if (obj instanceof IssuerAndSerialNumber) + { + return (IssuerAndSerialNumber)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new IssuerAndSerialNumber((ASN1Sequence)obj); + } + + throw new IllegalArgumentException( + "Illegal object in IssuerAndSerialNumber: " + obj.getClass().getName()); + } + + public IssuerAndSerialNumber( + ASN1Sequence seq) + { + this.name = X509Name.getInstance(seq.getObjectAt(0)); + this.serialNumber = (DERInteger)seq.getObjectAt(1); + } + + public IssuerAndSerialNumber( + X509Name name, + BigInteger serialNumber) + { + this.name = name; + this.serialNumber = new DERInteger(serialNumber); + } + + public IssuerAndSerialNumber( + X509Name name, + DERInteger serialNumber) + { + this.name = name; + this.serialNumber = serialNumber; + } + + public X509Name getName() + { + return name; + } + + public DERInteger getSerialNumber() + { + return serialNumber; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(name); + v.add(serialNumber); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/KEKIdentifier.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/KEKIdentifier.java new file mode 100644 index 000000000..291550e78 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/KEKIdentifier.java @@ -0,0 +1,139 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class KEKIdentifier + extends ASN1Encodable +{ + private ASN1OctetString keyIdentifier; + private DERGeneralizedTime date; + private OtherKeyAttribute other; + + public KEKIdentifier( + byte[] keyIdentifier, + DERGeneralizedTime date, + OtherKeyAttribute other) + { + this.keyIdentifier = new DEROctetString(keyIdentifier); + this.date = date; + this.other = other; + } + + public KEKIdentifier( + ASN1Sequence seq) + { + keyIdentifier = (ASN1OctetString)seq.getObjectAt(0); + + switch (seq.size()) + { + case 1: + break; + case 2: + if (seq.getObjectAt(1) instanceof DERGeneralizedTime) + { + date = (DERGeneralizedTime)seq.getObjectAt(1); + } + else + { + other = OtherKeyAttribute.getInstance(seq.getObjectAt(1)); + } + break; + case 3: + date = (DERGeneralizedTime)seq.getObjectAt(1); + other = OtherKeyAttribute.getInstance(seq.getObjectAt(2)); + break; + default: + throw new IllegalArgumentException("Invalid KEKIdentifier"); + } + } + + /** + * return a KEKIdentifier object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static KEKIdentifier getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * return a KEKIdentifier object from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static KEKIdentifier getInstance( + Object obj) + { + if (obj == null || obj instanceof KEKIdentifier) + { + return (KEKIdentifier)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new KEKIdentifier((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid KEKIdentifier: " + obj.getClass().getName()); + } + + public ASN1OctetString getKeyIdentifier() + { + return keyIdentifier; + } + + public DERGeneralizedTime getDate() + { + return date; + } + + public OtherKeyAttribute getOther() + { + return other; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * KEKIdentifier ::= SEQUENCE {
+     *     keyIdentifier OCTET STRING,
+     *     date GeneralizedTime OPTIONAL,
+     *     other OtherKeyAttribute OPTIONAL 
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(keyIdentifier); + + if (date != null) + { + v.add(date); + } + + if (other != null) + { + v.add(other); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/KEKRecipientInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/KEKRecipientInfo.java new file mode 100644 index 000000000..2c7a481b8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/KEKRecipientInfo.java @@ -0,0 +1,121 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class KEKRecipientInfo + extends ASN1Encodable +{ + private DERInteger version; + private KEKIdentifier kekid; + private AlgorithmIdentifier keyEncryptionAlgorithm; + private ASN1OctetString encryptedKey; + + public KEKRecipientInfo( + KEKIdentifier kekid, + AlgorithmIdentifier keyEncryptionAlgorithm, + ASN1OctetString encryptedKey) + { + this.version = new DERInteger(4); + this.kekid = kekid; + this.keyEncryptionAlgorithm = keyEncryptionAlgorithm; + this.encryptedKey = encryptedKey; + } + + public KEKRecipientInfo( + ASN1Sequence seq) + { + version = (DERInteger)seq.getObjectAt(0); + kekid = KEKIdentifier.getInstance(seq.getObjectAt(1)); + keyEncryptionAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(2)); + encryptedKey = (ASN1OctetString)seq.getObjectAt(3); + } + + /** + * return a KEKRecipientInfo object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static KEKRecipientInfo getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * return a KEKRecipientInfo object from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static KEKRecipientInfo getInstance( + Object obj) + { + if (obj == null || obj instanceof KEKRecipientInfo) + { + return (KEKRecipientInfo)obj; + } + + if(obj instanceof ASN1Sequence) + { + return new KEKRecipientInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid KEKRecipientInfo: " + obj.getClass().getName()); + } + + public DERInteger getVersion() + { + return version; + } + + public KEKIdentifier getKekid() + { + return kekid; + } + + public AlgorithmIdentifier getKeyEncryptionAlgorithm() + { + return keyEncryptionAlgorithm; + } + + public ASN1OctetString getEncryptedKey() + { + return encryptedKey; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * KEKRecipientInfo ::= SEQUENCE {
+     *     version CMSVersion,  -- always set to 4
+     *     kekid KEKIdentifier,
+     *     keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
+     *     encryptedKey EncryptedKey 
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(kekid); + v.add(keyEncryptionAlgorithm); + v.add(encryptedKey); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/KeyAgreeRecipientIdentifier.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/KeyAgreeRecipientIdentifier.java new file mode 100644 index 000000000..d9bb38a7f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/KeyAgreeRecipientIdentifier.java @@ -0,0 +1,103 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class KeyAgreeRecipientIdentifier + extends ASN1Encodable + implements ASN1Choice +{ + private IssuerAndSerialNumber issuerSerial; + private RecipientKeyIdentifier rKeyID; + + /** + * return an KeyAgreeRecipientIdentifier object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static KeyAgreeRecipientIdentifier getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * return an KeyAgreeRecipientIdentifier object from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static KeyAgreeRecipientIdentifier getInstance( + Object obj) + { + if (obj == null || obj instanceof KeyAgreeRecipientIdentifier) + { + return (KeyAgreeRecipientIdentifier)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new KeyAgreeRecipientIdentifier(IssuerAndSerialNumber.getInstance(obj)); + } + + if (obj instanceof ASN1TaggedObject && ((ASN1TaggedObject)obj).getTagNo() == 0) + { + return new KeyAgreeRecipientIdentifier(RecipientKeyIdentifier.getInstance( + (ASN1TaggedObject)obj, false)); + } + + throw new IllegalArgumentException("Invalid KeyAgreeRecipientIdentifier: " + obj.getClass().getName()); + } + + public KeyAgreeRecipientIdentifier( + IssuerAndSerialNumber issuerSerial) + { + this.issuerSerial = issuerSerial; + this.rKeyID = null; + } + + public KeyAgreeRecipientIdentifier( + RecipientKeyIdentifier rKeyID) + { + this.issuerSerial = null; + this.rKeyID = rKeyID; + } + + public IssuerAndSerialNumber getIssuerAndSerialNumber() + { + return issuerSerial; + } + + public RecipientKeyIdentifier getRKeyID() + { + return rKeyID; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * KeyAgreeRecipientIdentifier ::= CHOICE {
+     *     issuerAndSerialNumber IssuerAndSerialNumber,
+     *     rKeyId [0] IMPLICIT RecipientKeyIdentifier
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + if (issuerSerial != null) + { + return issuerSerial.toASN1Object(); + } + + return new DERTaggedObject(false, 0, rKeyID); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/KeyAgreeRecipientInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/KeyAgreeRecipientInfo.java new file mode 100644 index 000000000..a7145be02 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/KeyAgreeRecipientInfo.java @@ -0,0 +1,151 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class KeyAgreeRecipientInfo + extends ASN1Encodable +{ + private DERInteger version; + private OriginatorIdentifierOrKey originator; + private ASN1OctetString ukm; + private AlgorithmIdentifier keyEncryptionAlgorithm; + private ASN1Sequence recipientEncryptedKeys; + + public KeyAgreeRecipientInfo( + OriginatorIdentifierOrKey originator, + ASN1OctetString ukm, + AlgorithmIdentifier keyEncryptionAlgorithm, + ASN1Sequence recipientEncryptedKeys) + { + this.version = new DERInteger(3); + this.originator = originator; + this.ukm = ukm; + this.keyEncryptionAlgorithm = keyEncryptionAlgorithm; + this.recipientEncryptedKeys = recipientEncryptedKeys; + } + + public KeyAgreeRecipientInfo( + ASN1Sequence seq) + { + int index = 0; + + version = (DERInteger)seq.getObjectAt(index++); + originator = OriginatorIdentifierOrKey.getInstance( + (ASN1TaggedObject)seq.getObjectAt(index++), true); + + if (seq.getObjectAt(index) instanceof ASN1TaggedObject) + { + ukm = ASN1OctetString.getInstance( + (ASN1TaggedObject)seq.getObjectAt(index++), true); + } + + keyEncryptionAlgorithm = AlgorithmIdentifier.getInstance( + seq.getObjectAt(index++)); + + recipientEncryptedKeys = (ASN1Sequence)seq.getObjectAt(index++); + } + + /** + * return a KeyAgreeRecipientInfo object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static KeyAgreeRecipientInfo getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * return a KeyAgreeRecipientInfo object from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static KeyAgreeRecipientInfo getInstance( + Object obj) + { + if (obj == null || obj instanceof KeyAgreeRecipientInfo) + { + return (KeyAgreeRecipientInfo)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new KeyAgreeRecipientInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException( + "Illegal object in KeyAgreeRecipientInfo: " + obj.getClass().getName()); + + } + + public DERInteger getVersion() + { + return version; + } + + public OriginatorIdentifierOrKey getOriginator() + { + return originator; + } + + public ASN1OctetString getUserKeyingMaterial() + { + return ukm; + } + + public AlgorithmIdentifier getKeyEncryptionAlgorithm() + { + return keyEncryptionAlgorithm; + } + + public ASN1Sequence getRecipientEncryptedKeys() + { + return recipientEncryptedKeys; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * KeyAgreeRecipientInfo ::= SEQUENCE {
+     *     version CMSVersion,  -- always set to 3
+     *     originator [0] EXPLICIT OriginatorIdentifierOrKey,
+     *     ukm [1] EXPLICIT UserKeyingMaterial OPTIONAL,
+     *     keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
+     *     recipientEncryptedKeys RecipientEncryptedKeys 
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(new DERTaggedObject(true, 0, originator)); + + if (ukm != null) + { + v.add(new DERTaggedObject(true, 1, ukm)); + } + + v.add(keyEncryptionAlgorithm); + v.add(recipientEncryptedKeys); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/KeyTransRecipientInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/KeyTransRecipientInfo.java new file mode 100644 index 000000000..fbd8604c6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/KeyTransRecipientInfo.java @@ -0,0 +1,114 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class KeyTransRecipientInfo + extends ASN1Encodable +{ + private DERInteger version; + private RecipientIdentifier rid; + private AlgorithmIdentifier keyEncryptionAlgorithm; + private ASN1OctetString encryptedKey; + + public KeyTransRecipientInfo( + RecipientIdentifier rid, + AlgorithmIdentifier keyEncryptionAlgorithm, + ASN1OctetString encryptedKey) + { + if (rid.getDERObject() instanceof ASN1TaggedObject) + { + this.version = new DERInteger(2); + } + else + { + this.version = new DERInteger(0); + } + + this.rid = rid; + this.keyEncryptionAlgorithm = keyEncryptionAlgorithm; + this.encryptedKey = encryptedKey; + } + + public KeyTransRecipientInfo( + ASN1Sequence seq) + { + this.version = (DERInteger)seq.getObjectAt(0); + this.rid = RecipientIdentifier.getInstance(seq.getObjectAt(1)); + this.keyEncryptionAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(2)); + this.encryptedKey = (ASN1OctetString)seq.getObjectAt(3); + } + + /** + * return a KeyTransRecipientInfo object from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static KeyTransRecipientInfo getInstance( + Object obj) + { + if (obj == null || obj instanceof KeyTransRecipientInfo) + { + return (KeyTransRecipientInfo)obj; + } + + if(obj instanceof ASN1Sequence) + { + return new KeyTransRecipientInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException( + "Illegal object in KeyTransRecipientInfo: " + obj.getClass().getName()); + } + + public DERInteger getVersion() + { + return version; + } + + public RecipientIdentifier getRecipientIdentifier() + { + return rid; + } + + public AlgorithmIdentifier getKeyEncryptionAlgorithm() + { + return keyEncryptionAlgorithm; + } + + public ASN1OctetString getEncryptedKey() + { + return encryptedKey; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * KeyTransRecipientInfo ::= SEQUENCE {
+     *     version CMSVersion,  -- always set to 0 or 2
+     *     rid RecipientIdentifier,
+     *     keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
+     *     encryptedKey EncryptedKey 
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(rid); + v.add(keyEncryptionAlgorithm); + v.add(encryptedKey); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/OriginatorIdentifierOrKey.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/OriginatorIdentifierOrKey.java new file mode 100644 index 000000000..4fb58f0cd --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/OriginatorIdentifierOrKey.java @@ -0,0 +1,165 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.SubjectKeyIdentifier; + +public class OriginatorIdentifierOrKey + extends ASN1Encodable + implements ASN1Choice +{ + private DEREncodable id; + + public OriginatorIdentifierOrKey( + IssuerAndSerialNumber id) + { + this.id = id; + } + + /** + * @deprecated use version taking a SubjectKeyIdentifier + */ + public OriginatorIdentifierOrKey( + ASN1OctetString id) + { + this(new SubjectKeyIdentifier(id)); + } + + public OriginatorIdentifierOrKey( + SubjectKeyIdentifier id) + { + this.id = new DERTaggedObject(false, 0, id); + } + + public OriginatorIdentifierOrKey( + OriginatorPublicKey id) + { + this.id = new DERTaggedObject(false, 1, id); + } + + /** + * @deprecated use more specific version + */ + public OriginatorIdentifierOrKey( + DERObject id) + { + this.id = id; + } + + /** + * return an OriginatorIdentifierOrKey object from a tagged object. + * + * @param o the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static OriginatorIdentifierOrKey getInstance( + ASN1TaggedObject o, + boolean explicit) + { + if (!explicit) + { + throw new IllegalArgumentException( + "Can't implicitly tag OriginatorIdentifierOrKey"); + } + + return getInstance(o.getObject()); + } + + /** + * return an OriginatorIdentifierOrKey object from the given object. + * + * @param o the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static OriginatorIdentifierOrKey getInstance( + Object o) + { + if (o == null || o instanceof OriginatorIdentifierOrKey) + { + return (OriginatorIdentifierOrKey)o; + } + + if (o instanceof IssuerAndSerialNumber) + { + return new OriginatorIdentifierOrKey((IssuerAndSerialNumber)o); + } + + if (o instanceof SubjectKeyIdentifier) + { + return new OriginatorIdentifierOrKey((SubjectKeyIdentifier)o); + } + + if (o instanceof OriginatorPublicKey) + { + return new OriginatorIdentifierOrKey((OriginatorPublicKey)o); + } + + if (o instanceof ASN1TaggedObject) + { + // TODO Add validation + return new OriginatorIdentifierOrKey((ASN1TaggedObject)o); + } + + throw new IllegalArgumentException("Invalid OriginatorIdentifierOrKey: " + o.getClass().getName()); + } + + public DEREncodable getId() + { + return id; + } + + public IssuerAndSerialNumber getIssuerAndSerialNumber() + { + if (id instanceof IssuerAndSerialNumber) + { + return (IssuerAndSerialNumber)id; + } + + return null; + } + + public SubjectKeyIdentifier getSubjectKeyIdentifier() + { + if (id instanceof ASN1TaggedObject && ((ASN1TaggedObject)id).getTagNo() == 0) + { + return SubjectKeyIdentifier.getInstance((ASN1TaggedObject)id, false); + } + + return null; + } + + public OriginatorPublicKey getOriginatorKey() + { + if (id instanceof ASN1TaggedObject && ((ASN1TaggedObject)id).getTagNo() == 1) + { + return OriginatorPublicKey.getInstance((ASN1TaggedObject)id, false); + } + + return null; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * OriginatorIdentifierOrKey ::= CHOICE {
+     *     issuerAndSerialNumber IssuerAndSerialNumber,
+     *     subjectKeyIdentifier [0] SubjectKeyIdentifier,
+     *     originatorKey [1] OriginatorPublicKey 
+     * }
+     *
+     * SubjectKeyIdentifier ::= OCTET STRING
+     * 
+ */ + public DERObject toASN1Object() + { + return id.getDERObject(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/OriginatorInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/OriginatorInfo.java new file mode 100644 index 000000000..a36f2ec1e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/OriginatorInfo.java @@ -0,0 +1,129 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class OriginatorInfo + extends ASN1Encodable +{ + private ASN1Set certs; + private ASN1Set crls; + + public OriginatorInfo( + ASN1Set certs, + ASN1Set crls) + { + this.certs = certs; + this.crls = crls; + } + + public OriginatorInfo( + ASN1Sequence seq) + { + switch (seq.size()) + { + case 0: // empty + break; + case 1: + ASN1TaggedObject o = (ASN1TaggedObject)seq.getObjectAt(0); + switch (o.getTagNo()) + { + case 0 : + certs = ASN1Set.getInstance(o, false); + break; + case 1 : + crls = ASN1Set.getInstance(o, false); + break; + default: + throw new IllegalArgumentException("Bad tag in OriginatorInfo: " + o.getTagNo()); + } + break; + case 2: + certs = ASN1Set.getInstance((ASN1TaggedObject)seq.getObjectAt(0), false); + crls = ASN1Set.getInstance((ASN1TaggedObject)seq.getObjectAt(1), false); + break; + default: + throw new IllegalArgumentException("OriginatorInfo too big"); + } + } + + /** + * return an OriginatorInfo object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static OriginatorInfo getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * return an OriginatorInfo object from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static OriginatorInfo getInstance( + Object obj) + { + if (obj == null || obj instanceof OriginatorInfo) + { + return (OriginatorInfo)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new OriginatorInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid OriginatorInfo: " + obj.getClass().getName()); + } + + public ASN1Set getCertificates() + { + return certs; + } + + public ASN1Set getCRLs() + { + return crls; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * OriginatorInfo ::= SEQUENCE {
+     *     certs [0] IMPLICIT CertificateSet OPTIONAL,
+     *     crls [1] IMPLICIT CertificateRevocationLists OPTIONAL 
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (certs != null) + { + v.add(new DERTaggedObject(false, 0, certs)); + } + + if (crls != null) + { + v.add(new DERTaggedObject(false, 1, crls)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/OriginatorPublicKey.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/OriginatorPublicKey.java new file mode 100644 index 000000000..6558ce830 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/OriginatorPublicKey.java @@ -0,0 +1,100 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + + +public class OriginatorPublicKey + extends ASN1Encodable +{ + private AlgorithmIdentifier algorithm; + private DERBitString publicKey; + + public OriginatorPublicKey( + AlgorithmIdentifier algorithm, + byte[] publicKey) + { + this.algorithm = algorithm; + this.publicKey = new DERBitString(publicKey); + } + + public OriginatorPublicKey( + ASN1Sequence seq) + { + algorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(0)); + publicKey = (DERBitString)seq.getObjectAt(1); + } + + /** + * return an OriginatorPublicKey object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static OriginatorPublicKey getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * return an OriginatorPublicKey object from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static OriginatorPublicKey getInstance( + Object obj) + { + if (obj == null || obj instanceof OriginatorPublicKey) + { + return (OriginatorPublicKey)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new OriginatorPublicKey((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid OriginatorPublicKey: " + obj.getClass().getName()); + } + + public AlgorithmIdentifier getAlgorithm() + { + return algorithm; + } + + public DERBitString getPublicKey() + { + return publicKey; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * OriginatorPublicKey ::= SEQUENCE {
+     *     algorithm AlgorithmIdentifier,
+     *     publicKey BIT STRING 
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(algorithm); + v.add(publicKey); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/OtherKeyAttribute.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/OtherKeyAttribute.java new file mode 100644 index 000000000..84cbb11f6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/OtherKeyAttribute.java @@ -0,0 +1,82 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class OtherKeyAttribute + extends ASN1Encodable +{ + private DERObjectIdentifier keyAttrId; + private DEREncodable keyAttr; + + /** + * return an OtherKeyAttribute object from the given object. + * + * @param o the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static OtherKeyAttribute getInstance( + Object o) + { + if (o == null || o instanceof OtherKeyAttribute) + { + return (OtherKeyAttribute)o; + } + + if (o instanceof ASN1Sequence) + { + return new OtherKeyAttribute((ASN1Sequence)o); + } + + throw new IllegalArgumentException("unknown object in factory: " + o.getClass().getName()); + } + + public OtherKeyAttribute( + ASN1Sequence seq) + { + keyAttrId = (DERObjectIdentifier)seq.getObjectAt(0); + keyAttr = seq.getObjectAt(1); + } + + public OtherKeyAttribute( + DERObjectIdentifier keyAttrId, + DEREncodable keyAttr) + { + this.keyAttrId = keyAttrId; + this.keyAttr = keyAttr; + } + + public DERObjectIdentifier getKeyAttrId() + { + return keyAttrId; + } + + public DEREncodable getKeyAttr() + { + return keyAttr; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * OtherKeyAttribute ::= SEQUENCE {
+     *     keyAttrId OBJECT IDENTIFIER,
+     *     keyAttr ANY DEFINED BY keyAttrId OPTIONAL
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(keyAttrId); + v.add(keyAttr); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/OtherRecipientInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/OtherRecipientInfo.java new file mode 100644 index 000000000..d3c7d2629 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/OtherRecipientInfo.java @@ -0,0 +1,98 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class OtherRecipientInfo + extends ASN1Encodable +{ + private DERObjectIdentifier oriType; + private DEREncodable oriValue; + + public OtherRecipientInfo( + DERObjectIdentifier oriType, + DEREncodable oriValue) + { + this.oriType = oriType; + this.oriValue = oriValue; + } + + public OtherRecipientInfo( + ASN1Sequence seq) + { + oriType = DERObjectIdentifier.getInstance(seq.getObjectAt(0)); + oriValue = seq.getObjectAt(1); + } + + /** + * return a OtherRecipientInfo object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static OtherRecipientInfo getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * return a OtherRecipientInfo object from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static OtherRecipientInfo getInstance( + Object obj) + { + if (obj == null || obj instanceof OtherRecipientInfo) + { + return (OtherRecipientInfo)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new OtherRecipientInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid OtherRecipientInfo: " + obj.getClass().getName()); + } + + public DERObjectIdentifier getType() + { + return oriType; + } + + public DEREncodable getValue() + { + return oriValue; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * OtherRecipientInfo ::= SEQUENCE {
+     *    oriType OBJECT IDENTIFIER,
+     *    oriValue ANY DEFINED BY oriType }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(oriType); + v.add(oriValue); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/PasswordRecipientInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/PasswordRecipientInfo.java new file mode 100644 index 000000000..d5e07254b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/PasswordRecipientInfo.java @@ -0,0 +1,143 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class PasswordRecipientInfo + extends ASN1Encodable +{ + private DERInteger version; + private AlgorithmIdentifier keyDerivationAlgorithm; + private AlgorithmIdentifier keyEncryptionAlgorithm; + private ASN1OctetString encryptedKey; + + public PasswordRecipientInfo( + AlgorithmIdentifier keyEncryptionAlgorithm, + ASN1OctetString encryptedKey) + { + this.version = new DERInteger(0); + this.keyEncryptionAlgorithm = keyEncryptionAlgorithm; + this.encryptedKey = encryptedKey; + } + + public PasswordRecipientInfo( + AlgorithmIdentifier keyDerivationAlgorithm, + AlgorithmIdentifier keyEncryptionAlgorithm, + ASN1OctetString encryptedKey) + { + this.version = new DERInteger(0); + this.keyDerivationAlgorithm = keyDerivationAlgorithm; + this.keyEncryptionAlgorithm = keyEncryptionAlgorithm; + this.encryptedKey = encryptedKey; + } + + public PasswordRecipientInfo( + ASN1Sequence seq) + { + version = (DERInteger)seq.getObjectAt(0); + if (seq.getObjectAt(1) instanceof ASN1TaggedObject) + { + keyDerivationAlgorithm = AlgorithmIdentifier.getInstance((ASN1TaggedObject)seq.getObjectAt(1), false); + keyEncryptionAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(2)); + encryptedKey = (ASN1OctetString)seq.getObjectAt(3); + } + else + { + keyEncryptionAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(1)); + encryptedKey = (ASN1OctetString)seq.getObjectAt(2); + } + } + + /** + * return a PasswordRecipientInfo object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static PasswordRecipientInfo getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * return a PasswordRecipientInfo object from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static PasswordRecipientInfo getInstance( + Object obj) + { + if (obj == null || obj instanceof PasswordRecipientInfo) + { + return (PasswordRecipientInfo)obj; + } + + if(obj instanceof ASN1Sequence) + { + return new PasswordRecipientInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid PasswordRecipientInfo: " + obj.getClass().getName()); + } + + public DERInteger getVersion() + { + return version; + } + + public AlgorithmIdentifier getKeyDerivationAlgorithm() + { + return keyDerivationAlgorithm; + } + + public AlgorithmIdentifier getKeyEncryptionAlgorithm() + { + return keyEncryptionAlgorithm; + } + + public ASN1OctetString getEncryptedKey() + { + return encryptedKey; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * PasswordRecipientInfo ::= SEQUENCE {
+     *   version CMSVersion,   -- Always set to 0
+     *   keyDerivationAlgorithm [0] KeyDerivationAlgorithmIdentifier
+     *                             OPTIONAL,
+     *  keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
+     *  encryptedKey EncryptedKey }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + + if (keyDerivationAlgorithm != null) + { + v.add(new DERTaggedObject(false, 0, keyDerivationAlgorithm)); + } + v.add(keyEncryptionAlgorithm); + v.add(encryptedKey); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientEncryptedKey.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientEncryptedKey.java new file mode 100644 index 000000000..7868f097c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientEncryptedKey.java @@ -0,0 +1,99 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + + +public class RecipientEncryptedKey + extends ASN1Encodable +{ + private KeyAgreeRecipientIdentifier identifier; + private ASN1OctetString encryptedKey; + + private RecipientEncryptedKey( + ASN1Sequence seq) + { + identifier = KeyAgreeRecipientIdentifier.getInstance(seq.getObjectAt(0)); + encryptedKey = (ASN1OctetString)seq.getObjectAt(1); + } + + /** + * return an RecipientEncryptedKey object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static RecipientEncryptedKey getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * return a RecipientEncryptedKey object from the given object. + * + * @param obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static RecipientEncryptedKey getInstance( + Object obj) + { + if (obj == null || obj instanceof RecipientEncryptedKey) + { + return (RecipientEncryptedKey)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new RecipientEncryptedKey((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid RecipientEncryptedKey: " + obj.getClass().getName()); + } + + public RecipientEncryptedKey( + KeyAgreeRecipientIdentifier id, + ASN1OctetString encryptedKey) + { + this.identifier = id; + this.encryptedKey = encryptedKey; + } + + public KeyAgreeRecipientIdentifier getIdentifier() + { + return identifier; + } + + public ASN1OctetString getEncryptedKey() + { + return encryptedKey; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * RecipientEncryptedKey ::= SEQUENCE {
+     *     rid KeyAgreeRecipientIdentifier,
+     *     encryptedKey EncryptedKey
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(identifier); + v.add(encryptedKey); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientIdentifier.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientIdentifier.java new file mode 100644 index 000000000..bd2484a35 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientIdentifier.java @@ -0,0 +1,98 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class RecipientIdentifier + extends ASN1Encodable + implements ASN1Choice +{ + private DEREncodable id; + + public RecipientIdentifier( + IssuerAndSerialNumber id) + { + this.id = id; + } + + public RecipientIdentifier( + ASN1OctetString id) + { + this.id = new DERTaggedObject(false, 0, id); + } + + public RecipientIdentifier( + DERObject id) + { + this.id = id; + } + + /** + * return a RecipientIdentifier object from the given object. + * + * @param o the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static RecipientIdentifier getInstance( + Object o) + { + if (o == null || o instanceof RecipientIdentifier) + { + return (RecipientIdentifier)o; + } + + if (o instanceof IssuerAndSerialNumber) + { + return new RecipientIdentifier((IssuerAndSerialNumber)o); + } + + if (o instanceof ASN1OctetString) + { + return new RecipientIdentifier((ASN1OctetString)o); + } + + if (o instanceof DERObject) + { + return new RecipientIdentifier((DERObject)o); + } + + throw new IllegalArgumentException( + "Illegal object in RecipientIdentifier: " + o.getClass().getName()); + } + + public boolean isTagged() + { + return (id instanceof ASN1TaggedObject); + } + + public DEREncodable getId() + { + if (id instanceof ASN1TaggedObject) + { + return ASN1OctetString.getInstance((ASN1TaggedObject)id, false); + } + + return IssuerAndSerialNumber.getInstance(id); + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * RecipientIdentifier ::= CHOICE {
+     *     issuerAndSerialNumber IssuerAndSerialNumber,
+     *     subjectKeyIdentifier [0] SubjectKeyIdentifier 
+     * }
+     *
+     * SubjectKeyIdentifier ::= OCTET STRING
+     * 
+ */ + public DERObject toASN1Object() + { + return id.getDERObject(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientInfo.java new file mode 100644 index 000000000..70deade4d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientInfo.java @@ -0,0 +1,154 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class RecipientInfo + extends ASN1Encodable + implements ASN1Choice +{ + DEREncodable info; + + public RecipientInfo( + KeyTransRecipientInfo info) + { + this.info = info; + } + + public RecipientInfo( + KeyAgreeRecipientInfo info) + { + this.info = new DERTaggedObject(false, 1, info); + } + + public RecipientInfo( + KEKRecipientInfo info) + { + this.info = new DERTaggedObject(false, 2, info); + } + + public RecipientInfo( + PasswordRecipientInfo info) + { + this.info = new DERTaggedObject(false, 3, info); + } + + public RecipientInfo( + OtherRecipientInfo info) + { + this.info = new DERTaggedObject(false, 4, info); + } + + public RecipientInfo( + DERObject info) + { + this.info = info; + } + + public static RecipientInfo getInstance( + Object o) + { + if (o == null || o instanceof RecipientInfo) + { + return (RecipientInfo)o; + } + else if (o instanceof ASN1Sequence) + { + return new RecipientInfo((ASN1Sequence)o); + } + else if (o instanceof ASN1TaggedObject) + { + return new RecipientInfo((ASN1TaggedObject)o); + } + + throw new IllegalArgumentException("unknown object in factory: " + + o.getClass().getName()); + } + + public DERInteger getVersion() + { + if (info instanceof ASN1TaggedObject) + { + ASN1TaggedObject o = (ASN1TaggedObject)info; + + switch (o.getTagNo()) + { + case 1: + return KeyAgreeRecipientInfo.getInstance(o, false).getVersion(); + case 2: + return getKEKInfo(o).getVersion(); + case 3: + return PasswordRecipientInfo.getInstance(o, false).getVersion(); + case 4: + return new DERInteger(0); // no syntax version for OtherRecipientInfo + default: + throw new IllegalStateException("unknown tag"); + } + } + + return KeyTransRecipientInfo.getInstance(info).getVersion(); + } + + public boolean isTagged() + { + return (info instanceof ASN1TaggedObject); + } + + public DEREncodable getInfo() + { + if (info instanceof ASN1TaggedObject) + { + ASN1TaggedObject o = (ASN1TaggedObject)info; + + switch (o.getTagNo()) + { + case 1: + return KeyAgreeRecipientInfo.getInstance(o, false); + case 2: + return getKEKInfo(o); + case 3: + return PasswordRecipientInfo.getInstance(o, false); + case 4: + return OtherRecipientInfo.getInstance(o, false); + default: + throw new IllegalStateException("unknown tag"); + } + } + + return KeyTransRecipientInfo.getInstance(info); + } + + private KEKRecipientInfo getKEKInfo(ASN1TaggedObject o) + { + if (o.isExplicit()) + { // compatibilty with erroneous version + return KEKRecipientInfo.getInstance(o, true); + } + else + { + return KEKRecipientInfo.getInstance(o, false); + } + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * RecipientInfo ::= CHOICE {
+     *     ktri KeyTransRecipientInfo,
+     *     kari [1] KeyAgreeRecipientInfo,
+     *     kekri [2] KEKRecipientInfo,
+     *     pwri [3] PasswordRecipientInfo,
+     *     ori [4] OtherRecipientInfo }
+     * 
+ */ + public DERObject toASN1Object() + { + return info.getDERObject(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientKeyIdentifier.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientKeyIdentifier.java new file mode 100644 index 000000000..0f9215a0f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/RecipientKeyIdentifier.java @@ -0,0 +1,139 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class RecipientKeyIdentifier + extends ASN1Encodable +{ + private ASN1OctetString subjectKeyIdentifier; + private DERGeneralizedTime date; + private OtherKeyAttribute other; + + public RecipientKeyIdentifier( + ASN1OctetString subjectKeyIdentifier, + DERGeneralizedTime date, + OtherKeyAttribute other) + { + this.subjectKeyIdentifier = subjectKeyIdentifier; + this.date = date; + this.other = other; + } + + public RecipientKeyIdentifier( + ASN1Sequence seq) + { + subjectKeyIdentifier = ASN1OctetString.getInstance( + seq.getObjectAt(0)); + + switch(seq.size()) + { + case 1: + break; + case 2: + if (seq.getObjectAt(1) instanceof DERGeneralizedTime) + { + date = (DERGeneralizedTime)seq.getObjectAt(1); + } + else + { + other = OtherKeyAttribute.getInstance(seq.getObjectAt(2)); + } + break; + case 3: + date = (DERGeneralizedTime)seq.getObjectAt(1); + other = OtherKeyAttribute.getInstance(seq.getObjectAt(2)); + break; + default: + throw new IllegalArgumentException("Invalid RecipientKeyIdentifier"); + } + } + + /** + * return a RecipientKeyIdentifier object from a tagged object. + * + * @param _ato the tagged object holding the object we want. + * @param _explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @exception IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static RecipientKeyIdentifier getInstance(ASN1TaggedObject _ato, boolean _explicit) + { + return getInstance(ASN1Sequence.getInstance(_ato, _explicit)); + } + + /** + * return a RecipientKeyIdentifier object from the given object. + * + * @param _obj the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static RecipientKeyIdentifier getInstance(Object _obj) + { + if(_obj == null || _obj instanceof RecipientKeyIdentifier) + { + return (RecipientKeyIdentifier)_obj; + } + + if(_obj instanceof ASN1Sequence) + { + return new RecipientKeyIdentifier((ASN1Sequence)_obj); + } + + throw new IllegalArgumentException("Invalid RecipientKeyIdentifier: " + _obj.getClass().getName()); + } + + public ASN1OctetString getSubjectKeyIdentifier() + { + return subjectKeyIdentifier; + } + + public DERGeneralizedTime getDate() + { + return date; + } + + public OtherKeyAttribute getOtherKeyAttribute() + { + return other; + } + + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * RecipientKeyIdentifier ::= SEQUENCE {
+     *     subjectKeyIdentifier SubjectKeyIdentifier,
+     *     date GeneralizedTime OPTIONAL,
+     *     other OtherKeyAttribute OPTIONAL 
+     * }
+     *
+     * SubjectKeyIdentifier ::= OCTET STRING
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(subjectKeyIdentifier); + + if (date != null) + { + v.add(date); + } + + if (other != null) + { + v.add(other); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/SignedData.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/SignedData.java new file mode 100644 index 000000000..02803902b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/SignedData.java @@ -0,0 +1,306 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.BERSequence; +import com.google.bitcoin.bouncycastle.asn1.BERSet; +import com.google.bitcoin.bouncycastle.asn1.BERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +import java.util.Enumeration; + +/** + * a signed data object. + */ +public class SignedData + extends ASN1Encodable +{ + private DERInteger version; + private ASN1Set digestAlgorithms; + private ContentInfo contentInfo; + private ASN1Set certificates; + private ASN1Set crls; + private ASN1Set signerInfos; + private boolean certsBer; + private boolean crlsBer; + + public static SignedData getInstance( + Object o) + { + if (o instanceof SignedData) + { + return (SignedData)o; + } + else if (o instanceof ASN1Sequence) + { + return new SignedData((ASN1Sequence)o); + } + + throw new IllegalArgumentException("unknown object in factory: " + o.getClass().getName()); + } + + public SignedData( + ASN1Set digestAlgorithms, + ContentInfo contentInfo, + ASN1Set certificates, + ASN1Set crls, + ASN1Set signerInfos) + { + this.version = calculateVersion(contentInfo.getContentType(), certificates, crls, signerInfos); + this.digestAlgorithms = digestAlgorithms; + this.contentInfo = contentInfo; + this.certificates = certificates; + this.crls = crls; + this.signerInfos = signerInfos; + this.crlsBer = crls instanceof BERSet; + this.certsBer = certificates instanceof BERSet; + } + + + // RFC3852, section 5.1: + // IF ((certificates is present) AND + // (any certificates with a type of other are present)) OR + // ((crls is present) AND + // (any crls with a type of other are present)) + // THEN version MUST be 5 + // ELSE + // IF (certificates is present) AND + // (any version 2 attribute certificates are present) + // THEN version MUST be 4 + // ELSE + // IF ((certificates is present) AND + // (any version 1 attribute certificates are present)) OR + // (any SignerInfo structures are version 3) OR + // (encapContentInfo eContentType is other than id-data) + // THEN version MUST be 3 + // ELSE version MUST be 1 + // + private DERInteger calculateVersion( + DERObjectIdentifier contentOid, + ASN1Set certs, + ASN1Set crls, + ASN1Set signerInfs) + { + boolean otherCert = false; + boolean otherCrl = false; + boolean attrCertV1Found = false; + boolean attrCertV2Found = false; + + if (certs != null) + { + for (Enumeration en = certs.getObjects(); en.hasMoreElements();) + { + Object obj = en.nextElement(); + if (obj instanceof ASN1TaggedObject) + { + ASN1TaggedObject tagged = (ASN1TaggedObject)obj; + + if (tagged.getTagNo() == 1) + { + attrCertV1Found = true; + } + else if (tagged.getTagNo() == 2) + { + attrCertV2Found = true; + } + else if (tagged.getTagNo() == 3) + { + otherCert = true; + } + } + } + } + + if (otherCert) + { + return new DERInteger(5); + } + + if (crls != null) // no need to check if otherCert is true + { + for (Enumeration en = crls.getObjects(); en.hasMoreElements();) + { + Object obj = en.nextElement(); + if (obj instanceof ASN1TaggedObject) + { + otherCrl = true; + } + } + } + + if (otherCrl) + { + return new DERInteger(5); + } + + if (attrCertV2Found) + { + return new DERInteger(4); + } + + if (attrCertV1Found) + { + return new DERInteger(3); + } + + if (contentOid.equals(CMSObjectIdentifiers.data)) + { + if (checkForVersion3(signerInfs)) + { + return new DERInteger(3); + } + else + { + return new DERInteger(1); + } + } + else + { + return new DERInteger(3); + } + } + + private boolean checkForVersion3(ASN1Set signerInfs) + { + for (Enumeration e = signerInfs.getObjects(); e.hasMoreElements();) + { + SignerInfo s = SignerInfo.getInstance(e.nextElement()); + + if (s.getVersion().getValue().intValue() == 3) + { + return true; + } + } + + return false; + } + + public SignedData( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + version = (DERInteger)e.nextElement(); + digestAlgorithms = ((ASN1Set)e.nextElement()); + contentInfo = ContentInfo.getInstance(e.nextElement()); + + while (e.hasMoreElements()) + { + DERObject o = (DERObject)e.nextElement(); + + // + // an interesting feature of SignedData is that there appear + // to be varying implementations... + // for the moment we ignore anything which doesn't fit. + // + if (o instanceof ASN1TaggedObject) + { + ASN1TaggedObject tagged = (ASN1TaggedObject)o; + + switch (tagged.getTagNo()) + { + case 0: + certsBer = tagged instanceof BERTaggedObject; + certificates = ASN1Set.getInstance(tagged, false); + break; + case 1: + crlsBer = tagged instanceof BERTaggedObject; + crls = ASN1Set.getInstance(tagged, false); + break; + default: + throw new IllegalArgumentException("unknown tag value " + tagged.getTagNo()); + } + } + else + { + signerInfos = (ASN1Set)o; + } + } + } + + public DERInteger getVersion() + { + return version; + } + + public ASN1Set getDigestAlgorithms() + { + return digestAlgorithms; + } + + public ContentInfo getEncapContentInfo() + { + return contentInfo; + } + + public ASN1Set getCertificates() + { + return certificates; + } + + public ASN1Set getCRLs() + { + return crls; + } + + public ASN1Set getSignerInfos() + { + return signerInfos; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * SignedData ::= SEQUENCE {
+     *     version CMSVersion,
+     *     digestAlgorithms DigestAlgorithmIdentifiers,
+     *     encapContentInfo EncapsulatedContentInfo,
+     *     certificates [0] IMPLICIT CertificateSet OPTIONAL,
+     *     crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
+     *     signerInfos SignerInfos
+     *   }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(digestAlgorithms); + v.add(contentInfo); + + if (certificates != null) + { + if (certsBer) + { + v.add(new BERTaggedObject(false, 0, certificates)); + } + else + { + v.add(new DERTaggedObject(false, 0, certificates)); + } + } + + if (crls != null) + { + if (crlsBer) + { + v.add(new BERTaggedObject(false, 1, crls)); + } + else + { + v.add(new DERTaggedObject(false, 1, crls)); + } + } + + v.add(signerInfos); + + return new BERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/SignedDataParser.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/SignedDataParser.java new file mode 100644 index 000000000..823876897 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/SignedDataParser.java @@ -0,0 +1,139 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1SequenceParser; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.ASN1SetParser; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObjectParser; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERTags; + +import java.io.IOException; + +/** + *
+ * SignedData ::= SEQUENCE {
+ *     version CMSVersion,
+ *     digestAlgorithms DigestAlgorithmIdentifiers,
+ *     encapContentInfo EncapsulatedContentInfo,
+ *     certificates [0] IMPLICIT CertificateSet OPTIONAL,
+ *     crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
+ *     signerInfos SignerInfos
+ *   }
+ * 
+ */ +public class SignedDataParser +{ + private ASN1SequenceParser _seq; + private DERInteger _version; + private Object _nextObject; + private boolean _certsCalled; + private boolean _crlsCalled; + + public static SignedDataParser getInstance( + Object o) + throws IOException + { + if (o instanceof ASN1Sequence) + { + return new SignedDataParser(((ASN1Sequence)o).parser()); + } + if (o instanceof ASN1SequenceParser) + { + return new SignedDataParser((ASN1SequenceParser)o); + } + + throw new IOException("unknown object encountered: " + o.getClass().getName()); + } + + private SignedDataParser( + ASN1SequenceParser seq) + throws IOException + { + this._seq = seq; + this._version = (DERInteger)seq.readObject(); + } + + public DERInteger getVersion() + { + return _version; + } + + public ASN1SetParser getDigestAlgorithms() + throws IOException + { + Object o = _seq.readObject(); + + if (o instanceof ASN1Set) + { + return ((ASN1Set)o).parser(); + } + + return (ASN1SetParser)o; + } + + public ContentInfoParser getEncapContentInfo() + throws IOException + { + return new ContentInfoParser((ASN1SequenceParser)_seq.readObject()); + } + + public ASN1SetParser getCertificates() + throws IOException + { + _certsCalled = true; + _nextObject = _seq.readObject(); + + if (_nextObject instanceof ASN1TaggedObjectParser && ((ASN1TaggedObjectParser)_nextObject).getTagNo() == 0) + { + ASN1SetParser certs = (ASN1SetParser)((ASN1TaggedObjectParser)_nextObject).getObjectParser(DERTags.SET, false); + _nextObject = null; + + return certs; + } + + return null; + } + + public ASN1SetParser getCrls() + throws IOException + { + if (!_certsCalled) + { + throw new IOException("getCerts() has not been called."); + } + + _crlsCalled = true; + + if (_nextObject == null) + { + _nextObject = _seq.readObject(); + } + + if (_nextObject instanceof ASN1TaggedObjectParser && ((ASN1TaggedObjectParser)_nextObject).getTagNo() == 1) + { + ASN1SetParser crls = (ASN1SetParser)((ASN1TaggedObjectParser)_nextObject).getObjectParser(DERTags.SET, false); + _nextObject = null; + + return crls; + } + + return null; + } + + public ASN1SetParser getSignerInfos() + throws IOException + { + if (!_certsCalled || !_crlsCalled) + { + throw new IOException("getCerts() and/or getCrls() has not been called."); + } + + if (_nextObject == null) + { + _nextObject = _seq.readObject(); + } + + return (ASN1SetParser)_nextObject; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/SignerIdentifier.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/SignerIdentifier.java new file mode 100644 index 000000000..cfc80792d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/SignerIdentifier.java @@ -0,0 +1,98 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class SignerIdentifier + extends ASN1Encodable + implements ASN1Choice +{ + private DEREncodable id; + + public SignerIdentifier( + IssuerAndSerialNumber id) + { + this.id = id; + } + + public SignerIdentifier( + ASN1OctetString id) + { + this.id = new DERTaggedObject(false, 0, id); + } + + public SignerIdentifier( + DERObject id) + { + this.id = id; + } + + /** + * return a SignerIdentifier object from the given object. + * + * @param o the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static SignerIdentifier getInstance( + Object o) + { + if (o == null || o instanceof SignerIdentifier) + { + return (SignerIdentifier)o; + } + + if (o instanceof IssuerAndSerialNumber) + { + return new SignerIdentifier((IssuerAndSerialNumber)o); + } + + if (o instanceof ASN1OctetString) + { + return new SignerIdentifier((ASN1OctetString)o); + } + + if (o instanceof DERObject) + { + return new SignerIdentifier((DERObject)o); + } + + throw new IllegalArgumentException( + "Illegal object in SignerIdentifier: " + o.getClass().getName()); + } + + public boolean isTagged() + { + return (id instanceof ASN1TaggedObject); + } + + public DEREncodable getId() + { + if (id instanceof ASN1TaggedObject) + { + return ASN1OctetString.getInstance((ASN1TaggedObject)id, false); + } + + return id; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * SignerIdentifier ::= CHOICE {
+     *     issuerAndSerialNumber IssuerAndSerialNumber,
+     *     subjectKeyIdentifier [0] SubjectKeyIdentifier 
+     * }
+     *
+     * SubjectKeyIdentifier ::= OCTET STRING
+     * 
+ */ + public DERObject toASN1Object() + { + return id.getDERObject(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/SignerInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/SignerInfo.java new file mode 100644 index 000000000..3b23b6cde --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/SignerInfo.java @@ -0,0 +1,183 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +import java.util.Enumeration; + +public class SignerInfo + extends ASN1Encodable +{ + private DERInteger version; + private SignerIdentifier sid; + private AlgorithmIdentifier digAlgorithm; + private ASN1Set authenticatedAttributes; + private AlgorithmIdentifier digEncryptionAlgorithm; + private ASN1OctetString encryptedDigest; + private ASN1Set unauthenticatedAttributes; + + public static SignerInfo getInstance( + Object o) + throws IllegalArgumentException + { + if (o == null || o instanceof SignerInfo) + { + return (SignerInfo)o; + } + else if (o instanceof ASN1Sequence) + { + return new SignerInfo((ASN1Sequence)o); + } + + throw new IllegalArgumentException("unknown object in factory: " + o.getClass().getName()); + } + + public SignerInfo( + SignerIdentifier sid, + AlgorithmIdentifier digAlgorithm, + ASN1Set authenticatedAttributes, + AlgorithmIdentifier digEncryptionAlgorithm, + ASN1OctetString encryptedDigest, + ASN1Set unauthenticatedAttributes) + { + if (sid.isTagged()) + { + this.version = new DERInteger(3); + } + else + { + this.version = new DERInteger(1); + } + + this.sid = sid; + this.digAlgorithm = digAlgorithm; + this.authenticatedAttributes = authenticatedAttributes; + this.digEncryptionAlgorithm = digEncryptionAlgorithm; + this.encryptedDigest = encryptedDigest; + this.unauthenticatedAttributes = unauthenticatedAttributes; + } + + public SignerInfo( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + version = (DERInteger)e.nextElement(); + sid = SignerIdentifier.getInstance(e.nextElement()); + digAlgorithm = AlgorithmIdentifier.getInstance(e.nextElement()); + + Object obj = e.nextElement(); + + if (obj instanceof ASN1TaggedObject) + { + authenticatedAttributes = ASN1Set.getInstance((ASN1TaggedObject)obj, false); + + digEncryptionAlgorithm = AlgorithmIdentifier.getInstance(e.nextElement()); + } + else + { + authenticatedAttributes = null; + digEncryptionAlgorithm = AlgorithmIdentifier.getInstance(obj); + } + + encryptedDigest = DEROctetString.getInstance(e.nextElement()); + + if (e.hasMoreElements()) + { + unauthenticatedAttributes = ASN1Set.getInstance((ASN1TaggedObject)e.nextElement(), false); + } + else + { + unauthenticatedAttributes = null; + } + } + + public DERInteger getVersion() + { + return version; + } + + public SignerIdentifier getSID() + { + return sid; + } + + public ASN1Set getAuthenticatedAttributes() + { + return authenticatedAttributes; + } + + public AlgorithmIdentifier getDigestAlgorithm() + { + return digAlgorithm; + } + + public ASN1OctetString getEncryptedDigest() + { + return encryptedDigest; + } + + public AlgorithmIdentifier getDigestEncryptionAlgorithm() + { + return digEncryptionAlgorithm; + } + + public ASN1Set getUnauthenticatedAttributes() + { + return unauthenticatedAttributes; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  SignerInfo ::= SEQUENCE {
+     *      version Version,
+     *      SignerIdentifier sid,
+     *      digestAlgorithm DigestAlgorithmIdentifier,
+     *      authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL,
+     *      digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
+     *      encryptedDigest EncryptedDigest,
+     *      unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
+     *  }
+     *
+     *  EncryptedDigest ::= OCTET STRING
+     *
+     *  DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+     *
+     *  DigestEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(sid); + v.add(digAlgorithm); + + if (authenticatedAttributes != null) + { + v.add(new DERTaggedObject(false, 0, authenticatedAttributes)); + } + + v.add(digEncryptionAlgorithm); + v.add(encryptedDigest); + + if (unauthenticatedAttributes != null) + { + v.add(new DERTaggedObject(false, 1, unauthenticatedAttributes)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/Time.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/Time.java new file mode 100644 index 000000000..e2317f749 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/Time.java @@ -0,0 +1,128 @@ +package com.google.bitcoin.bouncycastle.asn1.cms; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERUTCTime; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.SimpleTimeZone; + +public class Time + extends ASN1Encodable + implements ASN1Choice +{ + DERObject time; + + public static Time getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); + } + + public Time( + DERObject time) + { + if (!(time instanceof DERUTCTime) + && !(time instanceof DERGeneralizedTime)) + { + throw new IllegalArgumentException("unknown object passed to Time"); + } + + this.time = time; + } + + /** + * creates a time object from a given date - if the date is between 1950 + * and 2049 a UTCTime object is generated, otherwise a GeneralizedTime + * is used. + */ + public Time( + Date date) + { + SimpleTimeZone tz = new SimpleTimeZone(0, "Z"); + SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmss"); + + dateF.setTimeZone(tz); + + String d = dateF.format(date) + "Z"; + int year = Integer.parseInt(d.substring(0, 4)); + + if (year < 1950 || year > 2049) + { + time = new DERGeneralizedTime(d); + } + else + { + time = new DERUTCTime(d.substring(2)); + } + } + + public static Time getInstance( + Object obj) + { + if (obj instanceof Time) + { + return (Time)obj; + } + else if (obj instanceof DERUTCTime) + { + return new Time((DERUTCTime)obj); + } + else if (obj instanceof DERGeneralizedTime) + { + return new Time((DERGeneralizedTime)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public String getTime() + { + if (time instanceof DERUTCTime) + { + return ((DERUTCTime)time).getAdjustedTime(); + } + else + { + return ((DERGeneralizedTime)time).getTime(); + } + } + + public Date getDate() + { + try + { + if (time instanceof DERUTCTime) + { + return ((DERUTCTime)time).getAdjustedDate(); + } + else + { + return ((DERGeneralizedTime)time).getDate(); + } + } + catch (ParseException e) + { // this should never happen + throw new IllegalStateException("invalid date string: " + e.getMessage()); + } + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * Time ::= CHOICE {
+     *             utcTime        UTCTime,
+     *             generalTime    GeneralizedTime }
+     * 
+ */ + public DERObject toASN1Object() + { + return time; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cms/ecc/MQVuserKeyingMaterial.java b/src/com/google/bitcoin/bouncycastle/asn1/cms/ecc/MQVuserKeyingMaterial.java new file mode 100644 index 000000000..2772d7e0a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cms/ecc/MQVuserKeyingMaterial.java @@ -0,0 +1,112 @@ +package com.google.bitcoin.bouncycastle.asn1.cms.ecc; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.cms.OriginatorPublicKey; + +public class MQVuserKeyingMaterial + extends ASN1Encodable +{ + private OriginatorPublicKey ephemeralPublicKey; + private ASN1OctetString addedukm; + + public MQVuserKeyingMaterial( + OriginatorPublicKey ephemeralPublicKey, + ASN1OctetString addedukm) + { + // TODO Check ephemeralPublicKey not null + + this.ephemeralPublicKey = ephemeralPublicKey; + this.addedukm = addedukm; + } + + private MQVuserKeyingMaterial( + ASN1Sequence seq) + { + // TODO Check seq has either 1 or 2 elements + + this.ephemeralPublicKey = OriginatorPublicKey.getInstance( + seq.getObjectAt(0)); + + if (seq.size() > 1) + { + this.addedukm = ASN1OctetString.getInstance( + (ASN1TaggedObject)seq.getObjectAt(1), true); + } + } + + /** + * return an MQVuserKeyingMaterial object from a tagged object. + * + * @param obj the tagged object holding the object we want. + * @param explicit true if the object is meant to be explicitly + * tagged false otherwise. + * @throws IllegalArgumentException if the object held by the + * tagged object cannot be converted. + */ + public static MQVuserKeyingMaterial getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * return an MQVuserKeyingMaterial object from the given object. + * + * @param obj the object we want converted. + * @throws IllegalArgumentException if the object cannot be converted. + */ + public static MQVuserKeyingMaterial getInstance( + Object obj) + { + if (obj == null || obj instanceof MQVuserKeyingMaterial) + { + return (MQVuserKeyingMaterial)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new MQVuserKeyingMaterial((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid MQVuserKeyingMaterial: " + obj.getClass().getName()); + } + + public OriginatorPublicKey getEphemeralPublicKey() + { + return ephemeralPublicKey; + } + + public ASN1OctetString getAddedukm() + { + return addedukm; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * MQVuserKeyingMaterial ::= SEQUENCE {
+     *   ephemeralPublicKey OriginatorPublicKey,
+     *   addedukm [0] EXPLICIT UserKeyingMaterial OPTIONAL  }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(ephemeralPublicKey); + + if (addedukm != null) + { + v.add(new DERTaggedObject(true, 0, addedukm)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/crmf/AttributeTypeAndValue.java b/src/com/google/bitcoin/bouncycastle/asn1/crmf/AttributeTypeAndValue.java new file mode 100644 index 000000000..8c17ef125 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/crmf/AttributeTypeAndValue.java @@ -0,0 +1,64 @@ +package com.google.bitcoin.bouncycastle.asn1.crmf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class AttributeTypeAndValue + extends ASN1Encodable +{ + private DERObjectIdentifier type; + private ASN1Encodable value; + + private AttributeTypeAndValue(ASN1Sequence seq) + { + type = (DERObjectIdentifier)seq.getObjectAt(0); + value = (ASN1Encodable)seq.getObjectAt(1); + } + + public static AttributeTypeAndValue getInstance(Object o) + { + if (o instanceof AttributeTypeAndValue) + { + return (AttributeTypeAndValue)o; + } + + if (o instanceof ASN1Sequence) + { + return new AttributeTypeAndValue((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public DERObjectIdentifier getType() + { + return type; + } + + public ASN1Encodable getValue() + { + return value; + } + + /** + *
+     * AttributeTypeAndValue ::= SEQUENCE {
+     *           type         OBJECT IDENTIFIER,
+     *           value        ANY DEFINED BY type }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(type); + v.add(value); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/crmf/CertId.java b/src/com/google/bitcoin/bouncycastle/asn1/crmf/CertId.java new file mode 100644 index 000000000..c5d8a7da0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/crmf/CertId.java @@ -0,0 +1,71 @@ +package com.google.bitcoin.bouncycastle.asn1.crmf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.GeneralName; + +public class CertId + extends ASN1Encodable +{ + private GeneralName issuer; + private DERInteger serialNumber; + + private CertId(ASN1Sequence seq) + { + issuer = GeneralName.getInstance(seq.getObjectAt(0)); + serialNumber = DERInteger.getInstance(seq.getObjectAt(1)); + } + + public static CertId getInstance(Object o) + { + if (o instanceof CertId) + { + return (CertId)o; + } + + if (o instanceof ASN1Sequence) + { + return new CertId((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public static CertId getInstance(ASN1TaggedObject obj, boolean isExplicit) + { + return getInstance(ASN1Sequence.getInstance(obj, isExplicit)); + } + + public GeneralName getIssuer() + { + return issuer; + } + + public DERInteger getSerialNumber() + { + return serialNumber; + } + + /** + *
+     * CertId ::= SEQUENCE {
+     *                 issuer           GeneralName,
+     *                 serialNumber     INTEGER }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(issuer); + v.add(serialNumber); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/crmf/CertReqMessages.java b/src/com/google/bitcoin/bouncycastle/asn1/crmf/CertReqMessages.java new file mode 100644 index 000000000..68e2445e3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/crmf/CertReqMessages.java @@ -0,0 +1,54 @@ +package com.google.bitcoin.bouncycastle.asn1.crmf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class CertReqMessages + extends ASN1Encodable +{ + private ASN1Sequence content; + + private CertReqMessages(ASN1Sequence seq) + { + content = seq; + } + + public static CertReqMessages getInstance(Object o) + { + if (o instanceof CertReqMessages) + { + return (CertReqMessages)o; + } + + if (o instanceof ASN1Sequence) + { + return new CertReqMessages((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public CertReqMsg[] toCertReqMsgArray() + { + CertReqMsg[] result = new CertReqMsg[content.size()]; + + for (int i = 0; i != result.length; i++) + { + result[i] = CertReqMsg.getInstance(content.getObjectAt(i)); + } + + return result; + } + + /** + *
+     * CertReqMessages ::= SEQUENCE SIZE (1..MAX) OF CertReqMsg
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + return content; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/crmf/CertReqMsg.java b/src/com/google/bitcoin/bouncycastle/asn1/crmf/CertReqMsg.java new file mode 100644 index 000000000..087c8bebc --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/crmf/CertReqMsg.java @@ -0,0 +1,110 @@ +package com.google.bitcoin.bouncycastle.asn1.crmf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +import java.util.Enumeration; + +public class CertReqMsg + extends ASN1Encodable +{ + private CertRequest certReq; + private ProofOfPossession pop; + private ASN1Sequence regInfo; + + private CertReqMsg(ASN1Sequence seq) + { + Enumeration en = seq.getObjects(); + + certReq = CertRequest.getInstance(en.nextElement()); + while (en.hasMoreElements()) + { + Object o = en.nextElement(); + + if (o instanceof ASN1TaggedObject) + { + pop = ProofOfPossession.getInstance(o); + } + else + { + regInfo = ASN1Sequence.getInstance(o); + } + } + } + + public static CertReqMsg getInstance(Object o) + { + if (o instanceof CertReqMsg) + { + return (CertReqMsg)o; + } + + if (o instanceof ASN1Sequence) + { + return new CertReqMsg((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public CertRequest getCertReq() + { + return certReq; + } + + public ProofOfPossession getPop() + { + return pop; + } + + public AttributeTypeAndValue[] getRegInfo() + { + if (regInfo == null) + { + return null; + } + + AttributeTypeAndValue[] results = new AttributeTypeAndValue[regInfo.size()]; + + for (int i = 0; i != results.length; i++) + { + results[i] = AttributeTypeAndValue.getInstance(regInfo.getObjectAt(i)); + } + + return results; + } + + /** + *
+     * CertReqMsg ::= SEQUENCE {
+     *                    certReq   CertRequest,
+     *                    pop       ProofOfPossession  OPTIONAL,
+     *                    -- content depends upon key type
+     *                    regInfo   SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue OPTIONAL }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(certReq); + + addOptional(v, pop); + addOptional(v, regInfo); + + return new DERSequence(v); + } + + private void addOptional(ASN1EncodableVector v, ASN1Encodable obj) + { + if (obj != null) + { + v.add(obj); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/crmf/CertRequest.java b/src/com/google/bitcoin/bouncycastle/asn1/crmf/CertRequest.java new file mode 100644 index 000000000..3b4e45160 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/crmf/CertRequest.java @@ -0,0 +1,80 @@ +package com.google.bitcoin.bouncycastle.asn1.crmf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class CertRequest + extends ASN1Encodable +{ + private DERInteger certReqId; + private CertTemplate certTemplate; + private Controls controls; + + private CertRequest(ASN1Sequence seq) + { + certReqId = DERInteger.getInstance(seq.getObjectAt(0)); + certTemplate = CertTemplate.getInstance(seq.getObjectAt(1)); + if (seq.size() > 2) + { + controls = Controls.getInstance(seq.getObjectAt(2)); + } + } + + public static CertRequest getInstance(Object o) + { + if (o instanceof CertRequest) + { + return (CertRequest)o; + } + + if (o instanceof ASN1Sequence) + { + return new CertRequest((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public DERInteger getCertReqId() + { + return certReqId; + } + + public CertTemplate getCertTemplate() + { + return certTemplate; + } + + public Controls getControls() + { + return controls; + } + + /** + *
+     * CertRequest ::= SEQUENCE {
+     *                      certReqId     INTEGER,          -- ID for matching request and reply
+     *                      certTemplate  CertTemplate,  -- Selected fields of cert to be issued
+     *                      controls      Controls OPTIONAL }   -- Attributes affecting issuance
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(certReqId); + v.add(certTemplate); + + if (controls != null) + { + v.add(controls); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/crmf/CertTemplate.java b/src/com/google/bitcoin/bouncycastle/asn1/crmf/CertTemplate.java new file mode 100644 index 000000000..44969580b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/crmf/CertTemplate.java @@ -0,0 +1,134 @@ +package com.google.bitcoin.bouncycastle.asn1.crmf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; +import com.google.bitcoin.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Extensions; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Name; + +import java.util.Enumeration; + +public class CertTemplate + extends ASN1Encodable +{ + private DERInteger version; + private DERInteger serialNumber; + private AlgorithmIdentifier signingAlg; + private X509Name issuer; + private OptionalValidity validity; + private X509Name subject; + private SubjectPublicKeyInfo publicKey; + private DERBitString issuerUID; + private DERBitString subjectUID; + private X509Extensions extensions; + + private CertTemplate(ASN1Sequence seq) + { + Enumeration en = seq.getObjects(); + while (en.hasMoreElements()) + { + ASN1TaggedObject tObj = (ASN1TaggedObject)en.nextElement(); + + switch (tObj.getTagNo()) + { + case 0: + version = DERInteger.getInstance(tObj, false); + break; + case 1: + serialNumber = DERInteger.getInstance(tObj, false); + break; + case 2: + signingAlg = AlgorithmIdentifier.getInstance(tObj, false); + break; + case 3: + issuer = X509Name.getInstance(tObj, true); // CHOICE + break; + case 4: + validity = OptionalValidity.getInstance(ASN1Sequence.getInstance(tObj, false)); + break; + case 5: + subject = X509Name.getInstance(tObj, true); // CHOICE + break; + case 6: + publicKey = SubjectPublicKeyInfo.getInstance(tObj, false); + break; + case 7: + issuerUID = DERBitString.getInstance(tObj, false); + break; + case 8: + subjectUID = DERBitString.getInstance(tObj, false); + break; + case 9: + extensions = X509Extensions.getInstance(tObj, false); + break; + default: + throw new IllegalArgumentException("unknown tag: " + tObj.getTagNo()); + } + } + } + + public static CertTemplate getInstance(Object o) + { + if (o instanceof CertTemplate) + { + return (CertTemplate)o; + } + + if (o instanceof ASN1Sequence) + { + return new CertTemplate((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + /** + *
+     *  CertTemplate ::= SEQUENCE {
+     *      version      [0] Version               OPTIONAL,
+     *      serialNumber [1] INTEGER               OPTIONAL,
+     *      signingAlg   [2] AlgorithmIdentifier   OPTIONAL,
+     *      issuer       [3] Name                  OPTIONAL,
+     *      validity     [4] OptionalValidity      OPTIONAL,
+     *      subject      [5] Name                  OPTIONAL,
+     *      publicKey    [6] SubjectPublicKeyInfo  OPTIONAL,
+     *      issuerUID    [7] UniqueIdentifier      OPTIONAL,
+     *      subjectUID   [8] UniqueIdentifier      OPTIONAL,
+     *      extensions   [9] Extensions            OPTIONAL }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + addOptional(v, 0, false, version); + addOptional(v, 1, false, serialNumber); + addOptional(v, 2, false, signingAlg); + addOptional(v, 3, true, issuer); // CHOICE + addOptional(v, 4, false, validity); + addOptional(v, 5, true, subject); // CHOICE + addOptional(v, 6, false, publicKey); + addOptional(v, 7, false, issuerUID); + addOptional(v, 8, false, subjectUID); + addOptional(v, 9, false, extensions); + + return new DERSequence(v); + } + + private void addOptional(ASN1EncodableVector v, int tagNo, boolean isExplicit, ASN1Encodable obj) + { + if (obj != null) + { + v.add(new DERTaggedObject(isExplicit, tagNo, obj)); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/crmf/Controls.java b/src/com/google/bitcoin/bouncycastle/asn1/crmf/Controls.java new file mode 100644 index 000000000..549838646 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/crmf/Controls.java @@ -0,0 +1,54 @@ +package com.google.bitcoin.bouncycastle.asn1.crmf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class Controls + extends ASN1Encodable +{ + private ASN1Sequence content; + + private Controls(ASN1Sequence seq) + { + content = seq; + } + + public static Controls getInstance(Object o) + { + if (o instanceof Controls) + { + return (Controls)o; + } + + if (o instanceof ASN1Sequence) + { + return new Controls((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public AttributeTypeAndValue[] toAttributeTypeAndValueArray() + { + AttributeTypeAndValue[] result = new AttributeTypeAndValue[content.size()]; + + for (int i = 0; i != result.length; i++) + { + result[i] = AttributeTypeAndValue.getInstance(content.getObjectAt(i)); + } + + return result; + } + + /** + *
+     * Controls  ::= SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + return content; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/crmf/EncryptedValue.java b/src/com/google/bitcoin/bouncycastle/asn1/crmf/EncryptedValue.java new file mode 100644 index 000000000..b77c662b9 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/crmf/EncryptedValue.java @@ -0,0 +1,113 @@ +package com.google.bitcoin.bouncycastle.asn1.crmf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class EncryptedValue + extends ASN1Encodable +{ + private AlgorithmIdentifier intendedAlg; + private AlgorithmIdentifier symmAlg; + private DERBitString encSymmKey; + private AlgorithmIdentifier keyAlg; + private ASN1OctetString valueHint; + private DERBitString encValue; + + private EncryptedValue(ASN1Sequence seq) + { + int index = 0; + while (seq.getObjectAt(index) instanceof ASN1TaggedObject) + { + ASN1TaggedObject tObj = (ASN1TaggedObject)seq.getObjectAt(index); + + switch (tObj.getTagNo()) + { + case 0: + intendedAlg = AlgorithmIdentifier.getInstance(tObj, false); + break; + case 1: + symmAlg = AlgorithmIdentifier.getInstance(tObj, false); + break; + case 2: + encSymmKey = DERBitString.getInstance(tObj, false); + break; + case 3: + keyAlg = AlgorithmIdentifier.getInstance(tObj, false); + break; + case 4: + valueHint = ASN1OctetString.getInstance(tObj, false); + break; + } + index++; + } + + encValue = DERBitString.getInstance(seq.getObjectAt(index)); + } + + public static EncryptedValue getInstance(Object o) + { + if (o instanceof EncryptedValue) + { + return (EncryptedValue)o; + } + + if (o instanceof ASN1Sequence) + { + return new EncryptedValue((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + /** + *
+     * EncryptedValue ::= SEQUENCE {
+     *                     intendedAlg   [0] AlgorithmIdentifier  OPTIONAL,
+     *                     -- the intended algorithm for which the value will be used
+     *                     symmAlg       [1] AlgorithmIdentifier  OPTIONAL,
+     *                     -- the symmetric algorithm used to encrypt the value
+     *                     encSymmKey    [2] BIT STRING           OPTIONAL,
+     *                     -- the (encrypted) symmetric key used to encrypt the value
+     *                     keyAlg        [3] AlgorithmIdentifier  OPTIONAL,
+     *                     -- algorithm used to encrypt the symmetric key
+     *                     valueHint     [4] OCTET STRING         OPTIONAL,
+     *                     -- a brief description or identifier of the encValue content
+     *                     -- (may be meaningful only to the sending entity, and used only
+     *                     -- if EncryptedValue might be re-examined by the sending entity
+     *                     -- in the future)
+     *                     encValue       BIT STRING }
+     *                     -- the encrypted value itself
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + addOptional(v, 0, intendedAlg); + addOptional(v, 1, symmAlg); + addOptional(v, 2, encSymmKey); + addOptional(v, 3, keyAlg); + addOptional(v, 4, valueHint); + + v.add(encValue); + + return new DERSequence(v); + } + + private void addOptional(ASN1EncodableVector v, int tagNo, ASN1Encodable obj) + { + if (obj != null) + { + v.add(new DERTaggedObject(false, tagNo, obj)); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/crmf/OptionalValidity.java b/src/com/google/bitcoin/bouncycastle/asn1/crmf/OptionalValidity.java new file mode 100644 index 000000000..19674e02a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/crmf/OptionalValidity.java @@ -0,0 +1,77 @@ +package com.google.bitcoin.bouncycastle.asn1.crmf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.Time; + +import java.util.Enumeration; + +public class OptionalValidity + extends ASN1Encodable +{ + private Time notBefore; + private Time notAfter; + + private OptionalValidity(ASN1Sequence seq) + { + Enumeration en = seq.getObjects(); + while (en.hasMoreElements()) + { + ASN1TaggedObject tObj = (ASN1TaggedObject)en.nextElement(); + + if (tObj.getTagNo() == 0) + { + notBefore = Time.getInstance(tObj, true); + } + else + { + notAfter = Time.getInstance(tObj, true); + } + } + } + + public static OptionalValidity getInstance(Object o) + { + if (o instanceof OptionalValidity) + { + return (OptionalValidity)o; + } + + if (o instanceof ASN1Sequence) + { + return new OptionalValidity((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + /** + *
+     * OptionalValidity ::= SEQUENCE {
+     *                        notBefore  [0] Time OPTIONAL,
+     *                        notAfter   [1] Time OPTIONAL } --at least one MUST be present
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (notBefore != null) + { + v.add(new DERTaggedObject(true, 0, notBefore)); + } + + if (notAfter != null) + { + v.add(new DERTaggedObject(true, 1, notAfter)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/crmf/PKIPublicationInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/crmf/PKIPublicationInfo.java new file mode 100644 index 000000000..76b94ddd0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/crmf/PKIPublicationInfo.java @@ -0,0 +1,81 @@ +package com.google.bitcoin.bouncycastle.asn1.crmf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class PKIPublicationInfo + extends ASN1Encodable +{ + private DERInteger action; + private ASN1Sequence pubInfos; + + private PKIPublicationInfo(ASN1Sequence seq) + { + action = DERInteger.getInstance(seq.getObjectAt(0)); + pubInfos = ASN1Sequence.getInstance(seq.getObjectAt(1)); + } + + public static PKIPublicationInfo getInstance(Object o) + { + if (o instanceof PKIPublicationInfo) + { + return (PKIPublicationInfo)o; + } + + if (o instanceof ASN1Sequence) + { + return new PKIPublicationInfo((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public DERInteger getAction() + { + return action; + } + + public SinglePubInfo[] getPubInfos() + { + if (pubInfos == null) + { + return null; + } + + SinglePubInfo[] results = new SinglePubInfo[pubInfos.size()]; + + for (int i = 0; i != results.length; i++) + { + results[i] = SinglePubInfo.getInstance(pubInfos.getObjectAt(i)); + } + + return results; + } + + /** + *
+     * PKIPublicationInfo ::= SEQUENCE {
+     *                  action     INTEGER {
+     *                                 dontPublish (0),
+     *                                 pleasePublish (1) },
+     *                  pubInfos  SEQUENCE SIZE (1..MAX) OF SinglePubInfo OPTIONAL }
+     * -- pubInfos MUST NOT be present if action is "dontPublish"
+     * -- (if action is "pleasePublish" and pubInfos is omitted,
+     * -- "dontCare" is assumed)
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(action); + v.add(pubInfos); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/crmf/POPOPrivKey.java b/src/com/google/bitcoin/bouncycastle/asn1/crmf/POPOPrivKey.java new file mode 100644 index 000000000..2399bf1d1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/crmf/POPOPrivKey.java @@ -0,0 +1,41 @@ +package com.google.bitcoin.bouncycastle.asn1.crmf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class POPOPrivKey + extends ASN1Encodable + implements ASN1Choice +{ + private DERObject obj; + + private POPOPrivKey(DERObject obj) + { + this.obj = obj; + } + + public static ASN1Encodable getInstance(ASN1TaggedObject tagged, boolean explicit) + { + return new POPOPrivKey(tagged.getObject()); // must be explictly tagged as choice + } + + /** + *
+     * POPOPrivKey ::= CHOICE {
+     *        thisMessage       [0] BIT STRING,         -- Deprecated
+     *         -- possession is proven in this message (which contains the private
+     *         -- key itself (encrypted for the CA))
+     *        subsequentMessage [1] SubsequentMessage,
+     *         -- possession will be proven in a subsequent message
+     *        dhMAC             [2] BIT STRING,         -- Deprecated
+     *        agreeMAC          [3] PKMACValue,
+     *        encryptedKey      [4] EnvelopedData }
+     * 
+ */ + public DERObject toASN1Object() + { + return obj; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/crmf/POPOSigningKey.java b/src/com/google/bitcoin/bouncycastle/asn1/crmf/POPOSigningKey.java new file mode 100644 index 000000000..8635fc61c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/crmf/POPOSigningKey.java @@ -0,0 +1,84 @@ +package com.google.bitcoin.bouncycastle.asn1.crmf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class POPOSigningKey + extends ASN1Encodable +{ + private POPOSigningKeyInput poposkInput; + private AlgorithmIdentifier algorithmIdentifier; + private DERBitString signature; + + private POPOSigningKey(ASN1Sequence seq) + { + int index = 0; + + if (seq.getObjectAt(0) instanceof ASN1TaggedObject) + { + poposkInput = POPOSigningKeyInput.getInstance(seq.getObjectAt(index++)); + } + algorithmIdentifier = AlgorithmIdentifier.getInstance(seq.getObjectAt(index++)); + signature = DERBitString.getInstance(seq.getObjectAt(index)); + } + + public static POPOSigningKey getInstance(Object o) + { + if (o instanceof POPOSigningKey) + { + return (POPOSigningKey)o; + } + + if (o instanceof ASN1Sequence) + { + return new POPOSigningKey((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public static POPOSigningKey getInstance(ASN1TaggedObject obj, boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + *
+     * POPOSigningKey ::= SEQUENCE {
+     *                      poposkInput           [0] POPOSigningKeyInput OPTIONAL,
+     *                      algorithmIdentifier   AlgorithmIdentifier,
+     *                      signature             BIT STRING }
+     *  -- The signature (using "algorithmIdentifier") is on the
+     *  -- DER-encoded value of poposkInput.  NOTE: If the CertReqMsg
+     *  -- certReq CertTemplate contains the subject and publicKey values,
+     *  -- then poposkInput MUST be omitted and the signature MUST be
+     *  -- computed on the DER-encoded value of CertReqMsg certReq.  If
+     *  -- the CertReqMsg certReq CertTemplate does not contain the public
+     *  -- key and subject values, then poposkInput MUST be present and
+     *  -- MUST be signed.  This strategy ensures that the public key is
+     *  -- not present in both the poposkInput and CertReqMsg certReq
+     *  -- CertTemplate fields.
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (poposkInput != null) + { + v.add(poposkInput); + } + + v.add(algorithmIdentifier); + v.add(signature); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/crmf/POPOSigningKeyInput.java b/src/com/google/bitcoin/bouncycastle/asn1/crmf/POPOSigningKeyInput.java new file mode 100644 index 000000000..bf498b061 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/crmf/POPOSigningKeyInput.java @@ -0,0 +1,67 @@ +package com.google.bitcoin.bouncycastle.asn1.crmf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.SubjectPublicKeyInfo; + +public class POPOSigningKeyInput + extends ASN1Encodable +{ + private ASN1Encodable authInfo; + private SubjectPublicKeyInfo publicKey; + + private POPOSigningKeyInput(ASN1Sequence seq) + { + authInfo = (ASN1Encodable)seq.getObjectAt(0); + publicKey = SubjectPublicKeyInfo.getInstance(seq.getObjectAt(1)); + } + + public static POPOSigningKeyInput getInstance(Object o) + { + if (o instanceof POPOSigningKeyInput) + { + return (POPOSigningKeyInput)o; + } + + if (o instanceof ASN1Sequence) + { + return new POPOSigningKeyInput((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public SubjectPublicKeyInfo getPublicKey() + { + return publicKey; + } + + /** + *
+     * POPOSigningKeyInput ::= SEQUENCE {
+     *        authInfo             CHOICE {
+     *                                 sender              [0] GeneralName,
+     *                                 -- used only if an authenticated identity has been
+     *                                 -- established for the sender (e.g., a DN from a
+     *                                 -- previously-issued and currently-valid certificate
+     *                                 publicKeyMAC        PKMACValue },
+     *                                 -- used if no authenticated GeneralName currently exists for
+     *                                 -- the sender; publicKeyMAC contains a password-based MAC
+     *                                 -- on the DER-encoded value of publicKey
+     *        publicKey           SubjectPublicKeyInfo }  -- from CertTemplate
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(authInfo); + v.add(publicKey); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/crmf/ProofOfPossession.java b/src/com/google/bitcoin/bouncycastle/asn1/crmf/ProofOfPossession.java new file mode 100644 index 000000000..244366d09 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/crmf/ProofOfPossession.java @@ -0,0 +1,78 @@ +package com.google.bitcoin.bouncycastle.asn1.crmf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERNull; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class ProofOfPossession + extends ASN1Encodable + implements ASN1Choice +{ + private int tagNo; + private ASN1Encodable obj; + + private ProofOfPossession(ASN1TaggedObject tagged) + { + tagNo = tagged.getTagNo(); + switch (tagNo) + { + case 0: + obj = DERNull.INSTANCE; + break; + case 1: + obj = POPOSigningKey.getInstance(tagged, false); + break; + case 2: + case 3: + obj = POPOPrivKey.getInstance(tagged, false); + break; + default: + throw new IllegalArgumentException("unknown tag: " + tagNo); + } + } + + public static ProofOfPossession getInstance(Object o) + { + if (o instanceof ProofOfPossession) + { + return (ProofOfPossession)o; + } + + if (o instanceof ASN1TaggedObject) + { + return new ProofOfPossession((ASN1TaggedObject)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public int getType() + { + return tagNo; + } + + public ASN1Encodable getObject() + { + return obj; + } + + /** + *
+     * ProofOfPossession ::= CHOICE {
+     *                           raVerified        [0] NULL,
+     *                           -- used if the RA has already verified that the requester is in
+     *                           -- possession of the private key
+     *                           signature         [1] POPOSigningKey,
+     *                           keyEncipherment   [2] POPOPrivKey,
+     *                           keyAgreement      [3] POPOPrivKey }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + return new DERTaggedObject(false, tagNo, obj); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/crmf/SinglePubInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/crmf/SinglePubInfo.java new file mode 100644 index 000000000..2e75cd888 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/crmf/SinglePubInfo.java @@ -0,0 +1,72 @@ +package com.google.bitcoin.bouncycastle.asn1.crmf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.GeneralName; + +public class SinglePubInfo + extends ASN1Encodable +{ + private DERInteger pubMethod; + private GeneralName pubLocation; + + private SinglePubInfo(ASN1Sequence seq) + { + pubMethod = DERInteger.getInstance(seq.getObjectAt(0)); + + if (seq.size() == 2) + { + pubLocation = GeneralName.getInstance(seq.getObjectAt(1)); + } + } + + public static SinglePubInfo getInstance(Object o) + { + if (o instanceof SinglePubInfo) + { + return (SinglePubInfo)o; + } + + if (o instanceof ASN1Sequence) + { + return new SinglePubInfo((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + public GeneralName getPubLocation() + { + return pubLocation; + } + + /** + *
+     * SinglePubInfo ::= SEQUENCE {
+     *        pubMethod    INTEGER {
+     *           dontCare    (0),
+     *           x500        (1),
+     *           web         (2),
+     *           ldap        (3) },
+     *       pubLocation  GeneralName OPTIONAL }
+     * 
+ * @return a basic ASN.1 object representation. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(pubMethod); + + if (pubLocation != null) + { + v.add(pubLocation); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/CryptoProObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/CryptoProObjectIdentifiers.java new file mode 100644 index 000000000..0b95c4d60 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/CryptoProObjectIdentifiers.java @@ -0,0 +1,45 @@ +package com.google.bitcoin.bouncycastle.asn1.cryptopro; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface CryptoProObjectIdentifiers +{ + // GOST Algorithms OBJECT IDENTIFIERS : + // { iso(1) member-body(2) ru(643) rans(2) cryptopro(2)} + static final String GOST_id = "1.2.643.2.2"; + + static final DERObjectIdentifier gostR3411 = new DERObjectIdentifier(GOST_id+".9"); + + static final DERObjectIdentifier gostR28147_cbc = new DERObjectIdentifier(GOST_id+".21"); + + static final DERObjectIdentifier gostR3410_94 = new DERObjectIdentifier(GOST_id+".20"); + static final DERObjectIdentifier gostR3410_2001 = new DERObjectIdentifier(GOST_id+".19"); + static final DERObjectIdentifier gostR3411_94_with_gostR3410_94 = new DERObjectIdentifier(GOST_id+".4"); + static final DERObjectIdentifier gostR3411_94_with_gostR3410_2001 = new DERObjectIdentifier(GOST_id+".3"); + + // { iso(1) member-body(2) ru(643) rans(2) cryptopro(2) hashes(30) } + static final DERObjectIdentifier gostR3411_94_CryptoProParamSet = new DERObjectIdentifier(GOST_id+".30.1"); + + // { iso(1) member-body(2) ru(643) rans(2) cryptopro(2) signs(32) } + static final DERObjectIdentifier gostR3410_94_CryptoPro_A = new DERObjectIdentifier(GOST_id+".32.2"); + static final DERObjectIdentifier gostR3410_94_CryptoPro_B = new DERObjectIdentifier(GOST_id+".32.3"); + static final DERObjectIdentifier gostR3410_94_CryptoPro_C = new DERObjectIdentifier(GOST_id+".32.4"); + static final DERObjectIdentifier gostR3410_94_CryptoPro_D = new DERObjectIdentifier(GOST_id+".32.5"); + + // { iso(1) member-body(2) ru(643) rans(2) cryptopro(2) exchanges(33) } + static final DERObjectIdentifier gostR3410_94_CryptoPro_XchA = new DERObjectIdentifier(GOST_id+".33.1"); + static final DERObjectIdentifier gostR3410_94_CryptoPro_XchB = new DERObjectIdentifier(GOST_id+".33.2"); + static final DERObjectIdentifier gostR3410_94_CryptoPro_XchC = new DERObjectIdentifier(GOST_id+".33.3"); + + //{ iso(1) member-body(2)ru(643) rans(2) cryptopro(2) ecc-signs(35) } + static final DERObjectIdentifier gostR3410_2001_CryptoPro_A = new DERObjectIdentifier(GOST_id+".35.1"); + static final DERObjectIdentifier gostR3410_2001_CryptoPro_B = new DERObjectIdentifier(GOST_id+".35.2"); + static final DERObjectIdentifier gostR3410_2001_CryptoPro_C = new DERObjectIdentifier(GOST_id+".35.3"); + + // { iso(1) member-body(2) ru(643) rans(2) cryptopro(2) ecc-exchanges(36) } + static final DERObjectIdentifier gostR3410_2001_CryptoPro_XchA = new DERObjectIdentifier(GOST_id+".36.0"); + static final DERObjectIdentifier gostR3410_2001_CryptoPro_XchB = new DERObjectIdentifier(GOST_id+".36.1"); + + static final DERObjectIdentifier gost_ElSgDH3410_default = new DERObjectIdentifier(GOST_id+".36.0"); + static final DERObjectIdentifier gost_ElSgDH3410_1 = new DERObjectIdentifier(GOST_id+".36.1"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/ECGOST3410NamedCurves.java b/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/ECGOST3410NamedCurves.java new file mode 100644 index 000000000..fe5d9491a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/ECGOST3410NamedCurves.java @@ -0,0 +1,168 @@ +package com.google.bitcoin.bouncycastle.asn1.cryptopro; + +import java.math.BigInteger; +import java.util.Enumeration; +import java.util.Hashtable; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.crypto.params.ECDomainParameters; +import com.google.bitcoin.bouncycastle.math.ec.ECCurve; +import com.google.bitcoin.bouncycastle.math.ec.ECFieldElement; +import com.google.bitcoin.bouncycastle.math.ec.ECPoint; + +/** + * table of the available named parameters for GOST 3410-2001. + */ +public class ECGOST3410NamedCurves +{ + static final Hashtable objIds = new Hashtable(); + static final Hashtable params = new Hashtable(); + static final Hashtable names = new Hashtable(); + + static + { + BigInteger mod_p = new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639319"); + BigInteger mod_q = new BigInteger("115792089237316195423570985008687907853073762908499243225378155805079068850323"); + + ECCurve.Fp curve = new ECCurve.Fp( + mod_p, // p + new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639316"), // a + new BigInteger("166")); // b + + ECDomainParameters ecParams = new ECDomainParameters( + curve, + new ECPoint.Fp(curve, + new ECFieldElement.Fp(curve.getQ(),new BigInteger("1")), // x + new ECFieldElement.Fp(curve.getQ(),new BigInteger("64033881142927202683649881450433473985931760268884941288852745803908878638612"))), // y + mod_q); + + params.put(CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_A, ecParams); + + mod_p = new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639319"); + mod_q = new BigInteger("115792089237316195423570985008687907853073762908499243225378155805079068850323"); + + curve = new ECCurve.Fp( + mod_p, // p + new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639316"), + new BigInteger("166")); + + ecParams = new ECDomainParameters( + curve, + new ECPoint.Fp(curve, + new ECFieldElement.Fp(curve.getQ(),new BigInteger("1")), // x + new ECFieldElement.Fp(curve.getQ(),new BigInteger("64033881142927202683649881450433473985931760268884941288852745803908878638612"))), // y + mod_q); + + params.put(CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_XchA, ecParams); + + mod_p = new BigInteger("57896044618658097711785492504343953926634992332820282019728792003956564823193"); //p + mod_q = new BigInteger("57896044618658097711785492504343953927102133160255826820068844496087732066703"); //q + + curve = new ECCurve.Fp( + mod_p, // p + new BigInteger("57896044618658097711785492504343953926634992332820282019728792003956564823190"), // a + new BigInteger("28091019353058090096996979000309560759124368558014865957655842872397301267595")); // b + + ecParams = new ECDomainParameters( + curve, + new ECPoint.Fp(curve, + new ECFieldElement.Fp(mod_p,new BigInteger("1")), // x + new ECFieldElement.Fp(mod_p,new BigInteger("28792665814854611296992347458380284135028636778229113005756334730996303888124"))), // y + mod_q); // q + + params.put(CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_B, ecParams); + + mod_p = new BigInteger("70390085352083305199547718019018437841079516630045180471284346843705633502619"); + mod_q = new BigInteger("70390085352083305199547718019018437840920882647164081035322601458352298396601"); + + curve = new ECCurve.Fp( + mod_p, // p + new BigInteger("70390085352083305199547718019018437841079516630045180471284346843705633502616"), + new BigInteger("32858")); + + ecParams = new ECDomainParameters( + curve, + new ECPoint.Fp(curve, + new ECFieldElement.Fp(mod_p,new BigInteger("0")), + new ECFieldElement.Fp(mod_p,new BigInteger("29818893917731240733471273240314769927240550812383695689146495261604565990247"))), + mod_q); + + params.put(CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_XchB, ecParams); + + mod_p = new BigInteger("70390085352083305199547718019018437841079516630045180471284346843705633502619"); //p + mod_q = new BigInteger("70390085352083305199547718019018437840920882647164081035322601458352298396601"); //q + curve = new ECCurve.Fp( + mod_p, // p + new BigInteger("70390085352083305199547718019018437841079516630045180471284346843705633502616"), // a + new BigInteger("32858")); // b + + ecParams = new ECDomainParameters( + curve, + new ECPoint.Fp(curve, + new ECFieldElement.Fp(mod_p,new BigInteger("0")), // x + new ECFieldElement.Fp(mod_p,new BigInteger("29818893917731240733471273240314769927240550812383695689146495261604565990247"))), // y + mod_q); // q + + params.put(CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_C, ecParams); + + objIds.put("GostR3410-2001-CryptoPro-A", CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_A); + objIds.put("GostR3410-2001-CryptoPro-B", CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_B); + objIds.put("GostR3410-2001-CryptoPro-C", CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_C); + objIds.put("GostR3410-2001-CryptoPro-XchA", CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_XchA); + objIds.put("GostR3410-2001-CryptoPro-XchB", CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_XchB); + + names.put(CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_A, "GostR3410-2001-CryptoPro-A"); + names.put(CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_B, "GostR3410-2001-CryptoPro-B"); + names.put(CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_C, "GostR3410-2001-CryptoPro-C"); + names.put(CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_XchA, "GostR3410-2001-CryptoPro-XchA"); + names.put(CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_XchB, "GostR3410-2001-CryptoPro-XchB"); + } + + /** + * return the ECDomainParameters object for the given OID, null if it + * isn't present. + * + * @param oid an object identifier representing a named parameters, if present. + */ + public static ECDomainParameters getByOID( + DERObjectIdentifier oid) + { + return (ECDomainParameters)params.get(oid); + } + + /** + * returns an enumeration containing the name strings for parameters + * contained in this structure. + */ + public static Enumeration getNames() + { + return objIds.keys(); + } + + public static ECDomainParameters getByName( + String name) + { + DERObjectIdentifier oid = (DERObjectIdentifier)objIds.get(name); + + if (oid != null) + { + return (ECDomainParameters)params.get(oid); + } + + return null; + } + + /** + * return the named curve name represented by the given object identifier. + */ + public static String getName( + DERObjectIdentifier oid) + { + return (String)names.get(oid); + } + + public static DERObjectIdentifier getOID(String name) + { + return (DERObjectIdentifier)objIds.get(name); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/ECGOST3410ParamSetParameters.java b/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/ECGOST3410ParamSetParameters.java new file mode 100644 index 000000000..54da1fa24 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/ECGOST3410ParamSetParameters.java @@ -0,0 +1,99 @@ +package com.google.bitcoin.bouncycastle.asn1.cryptopro; + +import java.math.BigInteger; +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class ECGOST3410ParamSetParameters + extends ASN1Encodable +{ + DERInteger p, q, a, b, x, y; + + public static ECGOST3410ParamSetParameters getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static ECGOST3410ParamSetParameters getInstance( + Object obj) + { + if(obj == null || obj instanceof ECGOST3410ParamSetParameters) + { + return (ECGOST3410ParamSetParameters)obj; + } + + if(obj instanceof ASN1Sequence) + { + return new ECGOST3410ParamSetParameters((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid GOST3410Parameter: " + obj.getClass().getName()); + } + + public ECGOST3410ParamSetParameters( + BigInteger a, + BigInteger b, + BigInteger p, + BigInteger q, + int x, + BigInteger y) + { + this.a = new DERInteger(a); + this.b = new DERInteger(b); + this.p = new DERInteger(p); + this.q = new DERInteger(q); + this.x = new DERInteger(x); + this.y = new DERInteger(y); + } + + public ECGOST3410ParamSetParameters( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + a = (DERInteger)e.nextElement(); + b = (DERInteger)e.nextElement(); + p = (DERInteger)e.nextElement(); + q = (DERInteger)e.nextElement(); + x = (DERInteger)e.nextElement(); + y = (DERInteger)e.nextElement(); + } + + public BigInteger getP() + { + return p.getPositiveValue(); + } + + public BigInteger getQ() + { + return q.getPositiveValue(); + } + + public BigInteger getA() + { + return a.getPositiveValue(); + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(a); + v.add(b); + v.add(p); + v.add(q); + v.add(x); + v.add(y); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST28147Parameters.java b/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST28147Parameters.java new file mode 100644 index 000000000..f682276ed --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST28147Parameters.java @@ -0,0 +1,72 @@ +package com.google.bitcoin.bouncycastle.asn1.cryptopro; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class GOST28147Parameters + extends ASN1Encodable +{ + ASN1OctetString iv; + DERObjectIdentifier paramSet; + + public static GOST28147Parameters getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static GOST28147Parameters getInstance( + Object obj) + { + if(obj == null || obj instanceof GOST28147Parameters) + { + return (GOST28147Parameters)obj; + } + + if(obj instanceof ASN1Sequence) + { + return new GOST28147Parameters((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid GOST3410Parameter: " + obj.getClass().getName()); + } + + public GOST28147Parameters( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + iv = (ASN1OctetString)e.nextElement(); + paramSet = (DERObjectIdentifier)e.nextElement(); + } + + /** + *
+     * Gost28147-89-Parameters ::=
+     *               SEQUENCE {
+     *                       iv                   Gost28147-89-IV,
+     *                       encryptionParamSet   OBJECT IDENTIFIER
+     *                }
+     *
+     *   Gost28147-89-IV ::= OCTET STRING (SIZE (8))
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(iv); + v.add(paramSet); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST3410NamedParameters.java b/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST3410NamedParameters.java new file mode 100644 index 000000000..3c6712303 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST3410NamedParameters.java @@ -0,0 +1,116 @@ +package com.google.bitcoin.bouncycastle.asn1.cryptopro; + +import java.math.BigInteger; +import java.util.Enumeration; +import java.util.Hashtable; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +/** + * table of the available named parameters for GOST 3410-94. + */ +public class GOST3410NamedParameters +{ + static final Hashtable objIds = new Hashtable(); + static final Hashtable params = new Hashtable(); + static final Hashtable names = new Hashtable(); + + static private GOST3410ParamSetParameters cryptoProA = new GOST3410ParamSetParameters( + 1024, + new BigInteger("127021248288932417465907042777176443525787653508916535812817507265705031260985098497423188333483401180925999995120988934130659205614996724254121049274349357074920312769561451689224110579311248812610229678534638401693520013288995000362260684222750813532307004517341633685004541062586971416883686778842537820383"), + new BigInteger("68363196144955700784444165611827252895102170888761442055095051287550314083023"), + new BigInteger("100997906755055304772081815535925224869841082572053457874823515875577147990529272777244152852699298796483356699682842027972896052747173175480590485607134746852141928680912561502802222185647539190902656116367847270145019066794290930185446216399730872221732889830323194097355403213400972588322876850946740663962") +// validationAlgorithm { +// algorithm +// id-GostR3410-94-bBis, +// parameters +// GostR3410-94-ValidationBisParameters: { +// x0 1376285941, +// c 3996757427 +// } +// } + + ); + + static private GOST3410ParamSetParameters cryptoProB = new GOST3410ParamSetParameters( + 1024, + new BigInteger("139454871199115825601409655107690713107041707059928031797758001454375765357722984094124368522288239833039114681648076688236921220737322672160740747771700911134550432053804647694904686120113087816240740184800477047157336662926249423571248823968542221753660143391485680840520336859458494803187341288580489525163"), + new BigInteger("79885141663410976897627118935756323747307951916507639758300472692338873533959"), + new BigInteger("42941826148615804143873447737955502392672345968607143066798112994089471231420027060385216699563848719957657284814898909770759462613437669456364882730370838934791080835932647976778601915343474400961034231316672578686920482194932878633360203384797092684342247621055760235016132614780652761028509445403338652341") +// validationAlgorithm { +// algorithm +// id-GostR3410-94-bBis, +// parameters +// GostR3410-94-ValidationBisParameters: { +// x0 1536654555, +// c 1855361757, +// d 14408629386140014567655 +//4902939282056547857802241461782996702017713059974755104394739915140 +//6115284791024439062735788342744854120601660303926203867703556828005 +//8957203818114895398976594425537561271800850306 +// } +// } +//} + ); + + static private GOST3410ParamSetParameters cryptoProXchA = new GOST3410ParamSetParameters( + 1024, + new BigInteger("142011741597563481196368286022318089743276138395243738762872573441927459393512718973631166078467600360848946623567625795282774719212241929071046134208380636394084512691828894000571524625445295769349356752728956831541775441763139384457191755096847107846595662547942312293338483924514339614727760681880609734239"), + new BigInteger("91771529896554605945588149018382750217296858393520724172743325725474374979801"), + new BigInteger("133531813272720673433859519948319001217942375967847486899482359599369642528734712461590403327731821410328012529253871914788598993103310567744136196364803064721377826656898686468463277710150809401182608770201615324990468332931294920912776241137878030224355746606283971659376426832674269780880061631528163475887") + ); + + static + { + params.put(CryptoProObjectIdentifiers.gostR3410_94_CryptoPro_A, cryptoProA); + params.put(CryptoProObjectIdentifiers.gostR3410_94_CryptoPro_B, cryptoProB); +// params.put(CryptoProObjectIdentifiers.gostR3410_94_CryptoPro_C, cryptoProC); +// params.put(CryptoProObjectIdentifiers.gostR3410_94_CryptoPro_D, cryptoProD); + params.put(CryptoProObjectIdentifiers.gostR3410_94_CryptoPro_XchA, cryptoProXchA); +// params.put(CryptoProObjectIdentifiers.gostR3410_94_CryptoPro_XchB, cryptoProXchA); +// params.put(CryptoProObjectIdentifiers.gostR3410_94_CryptoPro_XchC, cryptoProXchA); + + objIds.put("GostR3410-94-CryptoPro-A", CryptoProObjectIdentifiers.gostR3410_94_CryptoPro_A); + objIds.put("GostR3410-94-CryptoPro-B", CryptoProObjectIdentifiers.gostR3410_94_CryptoPro_B); + objIds.put("GostR3410-94-CryptoPro-XchA", CryptoProObjectIdentifiers.gostR3410_94_CryptoPro_XchA); + } + + /** + * return the GOST3410ParamSetParameters object for the given OID, null if it + * isn't present. + * + * @param oid an object identifier representing a named parameters, if present. + */ + public static GOST3410ParamSetParameters getByOID( + DERObjectIdentifier oid) + { + return (GOST3410ParamSetParameters)params.get(oid); + } + + /** + * returns an enumeration containing the name strings for parameters + * contained in this structure. + */ + public static Enumeration getNames() + { + return objIds.keys(); + } + + public static GOST3410ParamSetParameters getByName( + String name) + { + DERObjectIdentifier oid = (DERObjectIdentifier)objIds.get(name); + + if (oid != null) + { + return (GOST3410ParamSetParameters)params.get(oid); + } + + return null; + } + + public static DERObjectIdentifier getOID(String name) + { + return (DERObjectIdentifier)objIds.get(name); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST3410ParamSetParameters.java b/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST3410ParamSetParameters.java new file mode 100644 index 000000000..876a93f1b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST3410ParamSetParameters.java @@ -0,0 +1,105 @@ +package com.google.bitcoin.bouncycastle.asn1.cryptopro; + +import java.math.BigInteger; +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class GOST3410ParamSetParameters + extends ASN1Encodable +{ + int keySize; + DERInteger p, q, a; + + public static GOST3410ParamSetParameters getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static GOST3410ParamSetParameters getInstance( + Object obj) + { + if(obj == null || obj instanceof GOST3410ParamSetParameters) + { + return (GOST3410ParamSetParameters)obj; + } + + if(obj instanceof ASN1Sequence) + { + return new GOST3410ParamSetParameters((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid GOST3410Parameter: " + obj.getClass().getName()); + } + + public GOST3410ParamSetParameters( + int keySize, + BigInteger p, + BigInteger q, + BigInteger a) + { + this.keySize = keySize; + this.p = new DERInteger(p); + this.q = new DERInteger(q); + this.a = new DERInteger(a); + } + + public GOST3410ParamSetParameters( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + keySize = ((DERInteger)e.nextElement()).getValue().intValue(); + p = (DERInteger)e.nextElement(); + q = (DERInteger)e.nextElement(); + a = (DERInteger)e.nextElement(); + } + + /** + * @deprecated use getKeySize + */ + public int getLKeySize() + { + return keySize; + } + + public int getKeySize() + { + return keySize; + } + + public BigInteger getP() + { + return p.getPositiveValue(); + } + + public BigInteger getQ() + { + return q.getPositiveValue(); + } + + public BigInteger getA() + { + return a.getPositiveValue(); + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new DERInteger(keySize)); + v.add(p); + v.add(q); + v.add(a); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST3410PublicKeyAlgParameters.java b/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST3410PublicKeyAlgParameters.java new file mode 100644 index 000000000..2f2ff5afa --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/cryptopro/GOST3410PublicKeyAlgParameters.java @@ -0,0 +1,101 @@ +package com.google.bitcoin.bouncycastle.asn1.cryptopro; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class GOST3410PublicKeyAlgParameters + extends ASN1Encodable +{ + private DERObjectIdentifier publicKeyParamSet; + private DERObjectIdentifier digestParamSet; + private DERObjectIdentifier encryptionParamSet; + + public static GOST3410PublicKeyAlgParameters getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static GOST3410PublicKeyAlgParameters getInstance( + Object obj) + { + if(obj == null || obj instanceof GOST3410PublicKeyAlgParameters) + { + return (GOST3410PublicKeyAlgParameters)obj; + } + + if(obj instanceof ASN1Sequence) + { + return new GOST3410PublicKeyAlgParameters((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid GOST3410Parameter: " + obj.getClass().getName()); + } + + public GOST3410PublicKeyAlgParameters( + DERObjectIdentifier publicKeyParamSet, + DERObjectIdentifier digestParamSet) + { + this.publicKeyParamSet = publicKeyParamSet; + this.digestParamSet = digestParamSet; + this.encryptionParamSet = null; + } + + public GOST3410PublicKeyAlgParameters( + DERObjectIdentifier publicKeyParamSet, + DERObjectIdentifier digestParamSet, + DERObjectIdentifier encryptionParamSet) + { + this.publicKeyParamSet = publicKeyParamSet; + this.digestParamSet = digestParamSet; + this.encryptionParamSet = encryptionParamSet; + } + + public GOST3410PublicKeyAlgParameters( + ASN1Sequence seq) + { + this.publicKeyParamSet = (DERObjectIdentifier)seq.getObjectAt(0); + this.digestParamSet = (DERObjectIdentifier)seq.getObjectAt(1); + + if (seq.size() > 2) + { + this.encryptionParamSet = (DERObjectIdentifier)seq.getObjectAt(2); + } + } + + public DERObjectIdentifier getPublicKeyParamSet() + { + return publicKeyParamSet; + } + + public DERObjectIdentifier getDigestParamSet() + { + return digestParamSet; + } + + public DERObjectIdentifier getEncryptionParamSet() + { + return encryptionParamSet; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(publicKeyParamSet); + v.add(digestParamSet); + + if (encryptionParamSet != null) + { + v.add(encryptionParamSet); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/eac/EACObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/eac/EACObjectIdentifiers.java new file mode 100644 index 000000000..6353c0580 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/eac/EACObjectIdentifiers.java @@ -0,0 +1,55 @@ +package com.google.bitcoin.bouncycastle.asn1.eac; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface EACObjectIdentifiers +{ + // bsi-de OBJECT IDENTIFIER ::= { + // itu-t(0) identified-organization(4) etsi(0) + // reserved(127) etsi-identified-organization(0) 7 + // } + static final DERObjectIdentifier bsi_de = new DERObjectIdentifier("0.4.0.127.0.7"); + + // id-PK OBJECT IDENTIFIER ::= { + // bsi-de protocols(2) smartcard(2) 1 + // } + static final DERObjectIdentifier id_PK = new DERObjectIdentifier(bsi_de + ".2.2.1"); + + static final DERObjectIdentifier id_PK_DH = new DERObjectIdentifier(id_PK + ".1"); + static final DERObjectIdentifier id_PK_ECDH = new DERObjectIdentifier(id_PK + ".2"); + + // id-CA OBJECT IDENTIFIER ::= { + // bsi-de protocols(2) smartcard(2) 3 + // } + static final DERObjectIdentifier id_CA = new DERObjectIdentifier(bsi_de + ".2.2.3"); + static final DERObjectIdentifier id_CA_DH = new DERObjectIdentifier(id_CA + ".1"); + static final DERObjectIdentifier id_CA_DH_3DES_CBC_CBC = new DERObjectIdentifier(id_CA_DH + ".1"); + static final DERObjectIdentifier id_CA_ECDH = new DERObjectIdentifier(id_CA + ".2"); + static final DERObjectIdentifier id_CA_ECDH_3DES_CBC_CBC = new DERObjectIdentifier(id_CA_ECDH + ".1"); + + // + // id-TA OBJECT IDENTIFIER ::= { + // bsi-de protocols(2) smartcard(2) 2 + // } + static final DERObjectIdentifier id_TA = new DERObjectIdentifier(bsi_de + ".2.2.2"); + + static final DERObjectIdentifier id_TA_RSA = new DERObjectIdentifier(id_TA + ".1"); + static final DERObjectIdentifier id_TA_RSA_v1_5_SHA_1 = new DERObjectIdentifier(id_TA_RSA + ".1"); + static final DERObjectIdentifier id_TA_RSA_v1_5_SHA_256 = new DERObjectIdentifier(id_TA_RSA + ".2"); + static final DERObjectIdentifier id_TA_RSA_PSS_SHA_1 = new DERObjectIdentifier(id_TA_RSA + ".3"); + static final DERObjectIdentifier id_TA_RSA_PSS_SHA_256 = new DERObjectIdentifier(id_TA_RSA + ".4"); + static final DERObjectIdentifier id_TA_ECDSA = new DERObjectIdentifier(id_TA + ".2"); + static final DERObjectIdentifier id_TA_ECDSA_SHA_1 = new DERObjectIdentifier(id_TA_ECDSA + ".1"); + static final DERObjectIdentifier id_TA_ECDSA_SHA_224 = new DERObjectIdentifier(id_TA_ECDSA + ".2"); + static final DERObjectIdentifier id_TA_ECDSA_SHA_256 = new DERObjectIdentifier(id_TA_ECDSA + ".3"); + + static final DERObjectIdentifier id_TA_ECDSA_SHA_384 = new DERObjectIdentifier(id_TA_ECDSA + ".4"); + static final DERObjectIdentifier id_TA_ECDSA_SHA_512 = new DERObjectIdentifier(id_TA_ECDSA + ".5"); + + /** + * id-EAC-ePassport OBJECT IDENTIFIER ::= { + * bsi-de applications(3) mrtd(1) roles(2) 1} + */ + static final DERObjectIdentifier id_EAC_ePassport = new DERObjectIdentifier(bsi_de + ".3.1.2.1"); + +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/esf/CommitmentTypeIdentifier.java b/src/com/google/bitcoin/bouncycastle/asn1/esf/CommitmentTypeIdentifier.java new file mode 100644 index 000000000..57c28dab8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/esf/CommitmentTypeIdentifier.java @@ -0,0 +1,14 @@ +package com.google.bitcoin.bouncycastle.asn1.esf; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; + +public interface CommitmentTypeIdentifier +{ + public static final DERObjectIdentifier proofOfOrigin = PKCSObjectIdentifiers.id_cti_ets_proofOfOrigin; + public static final DERObjectIdentifier proofOfReceipt = PKCSObjectIdentifiers.id_cti_ets_proofOfReceipt; + public static final DERObjectIdentifier proofOfDelivery = PKCSObjectIdentifiers.id_cti_ets_proofOfDelivery; + public static final DERObjectIdentifier proofOfSender = PKCSObjectIdentifiers.id_cti_ets_proofOfSender; + public static final DERObjectIdentifier proofOfApproval = PKCSObjectIdentifiers.id_cti_ets_proofOfApproval; + public static final DERObjectIdentifier proofOfCreation = PKCSObjectIdentifiers.id_cti_ets_proofOfCreation; +} \ No newline at end of file diff --git a/src/com/google/bitcoin/bouncycastle/asn1/esf/CommitmentTypeIndication.java b/src/com/google/bitcoin/bouncycastle/asn1/esf/CommitmentTypeIndication.java new file mode 100644 index 000000000..6dda1b868 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/esf/CommitmentTypeIndication.java @@ -0,0 +1,83 @@ +package com.google.bitcoin.bouncycastle.asn1.esf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class CommitmentTypeIndication + extends ASN1Encodable +{ + private DERObjectIdentifier commitmentTypeId; + private ASN1Sequence commitmentTypeQualifier; + + public CommitmentTypeIndication( + ASN1Sequence seq) + { + commitmentTypeId = (DERObjectIdentifier)seq.getObjectAt(0); + + if (seq.size() > 1) + { + commitmentTypeQualifier = (ASN1Sequence)seq.getObjectAt(1); + } + } + + public CommitmentTypeIndication( + DERObjectIdentifier commitmentTypeId) + { + this.commitmentTypeId = commitmentTypeId; + } + + public CommitmentTypeIndication( + DERObjectIdentifier commitmentTypeId, + ASN1Sequence commitmentTypeQualifier) + { + this.commitmentTypeId = commitmentTypeId; + this.commitmentTypeQualifier = commitmentTypeQualifier; + } + + public static CommitmentTypeIndication getInstance( + Object obj) + { + if (obj == null || obj instanceof CommitmentTypeIndication) + { + return (CommitmentTypeIndication)obj; + } + + return new CommitmentTypeIndication(ASN1Sequence.getInstance(obj)); + } + + public DERObjectIdentifier getCommitmentTypeId() + { + return commitmentTypeId; + } + + public ASN1Sequence getCommitmentTypeQualifier() + { + return commitmentTypeQualifier; + } + + /** + *
+     * CommitmentTypeIndication ::= SEQUENCE {
+     *      commitmentTypeId   CommitmentTypeIdentifier,
+     *      commitmentTypeQualifier   SEQUENCE SIZE (1..MAX) OF
+     *              CommitmentTypeQualifier OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(commitmentTypeId); + + if (commitmentTypeQualifier != null) + { + v.add(commitmentTypeQualifier); + } + + return new DERSequence(v); + } +} \ No newline at end of file diff --git a/src/com/google/bitcoin/bouncycastle/asn1/esf/CommitmentTypeQualifier.java b/src/com/google/bitcoin/bouncycastle/asn1/esf/CommitmentTypeQualifier.java new file mode 100644 index 000000000..a5beb5e9d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/esf/CommitmentTypeQualifier.java @@ -0,0 +1,108 @@ +package com.google.bitcoin.bouncycastle.asn1.esf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * Commitment type qualifiers, used in the Commitment-Type-Indication attribute (RFC3126). + * + *
+ *   CommitmentTypeQualifier ::= SEQUENCE {
+ *       commitmentTypeIdentifier  CommitmentTypeIdentifier,
+ *       qualifier          ANY DEFINED BY commitmentTypeIdentifier OPTIONAL }
+ * 
+ */ +public class CommitmentTypeQualifier + extends ASN1Encodable +{ + private DERObjectIdentifier commitmentTypeIdentifier; + private DEREncodable qualifier; + + /** + * Creates a new CommitmentTypeQualifier instance. + * + * @param commitmentTypeIdentifier a CommitmentTypeIdentifier value + */ + public CommitmentTypeQualifier( + DERObjectIdentifier commitmentTypeIdentifier) + { + this(commitmentTypeIdentifier, null); + } + + /** + * Creates a new CommitmentTypeQualifier instance. + * + * @param commitmentTypeIdentifier a CommitmentTypeIdentifier value + * @param qualifier the qualifier, defined by the above field. + */ + public CommitmentTypeQualifier( + DERObjectIdentifier commitmentTypeIdentifier, + DEREncodable qualifier) + { + this.commitmentTypeIdentifier = commitmentTypeIdentifier; + this.qualifier = qualifier; + } + + /** + * Creates a new CommitmentTypeQualifier instance. + * + * @param as CommitmentTypeQualifier structure + * encoded as an ASN1Sequence. + */ + public CommitmentTypeQualifier( + ASN1Sequence as) + { + commitmentTypeIdentifier = (DERObjectIdentifier)as.getObjectAt(0); + + if (as.size() > 1) + { + qualifier = as.getObjectAt(1); + } + } + + public static CommitmentTypeQualifier getInstance(Object as) + { + if (as instanceof CommitmentTypeQualifier || as == null) + { + return (CommitmentTypeQualifier)as; + } + else if (as instanceof ASN1Sequence) + { + return new CommitmentTypeQualifier((ASN1Sequence)as); + } + + throw new IllegalArgumentException("unknown object in getInstance."); + } + + public DERObjectIdentifier getCommitmentTypeIdentifier() + { + return commitmentTypeIdentifier; + } + + public DEREncodable getQualifier() + { + return qualifier; + } + + /** + * Returns a DER-encodable representation of this instance. + * + * @return a DERObject value + */ + public DERObject toASN1Object() + { + ASN1EncodableVector dev = new ASN1EncodableVector(); + dev.add(commitmentTypeIdentifier); + if (qualifier != null) + { + dev.add(qualifier); + } + + return new DERSequence(dev); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/esf/ESFAttributes.java b/src/com/google/bitcoin/bouncycastle/asn1/esf/ESFAttributes.java new file mode 100644 index 000000000..266b06a1f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/esf/ESFAttributes.java @@ -0,0 +1,21 @@ +package com.google.bitcoin.bouncycastle.asn1.esf; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; + +public interface ESFAttributes +{ + public static final DERObjectIdentifier sigPolicyId = PKCSObjectIdentifiers.id_aa_ets_sigPolicyId; + public static final DERObjectIdentifier commitmentType = PKCSObjectIdentifiers.id_aa_ets_commitmentType; + public static final DERObjectIdentifier signerLocation = PKCSObjectIdentifiers.id_aa_ets_signerLocation; + public static final DERObjectIdentifier signerAttr = PKCSObjectIdentifiers.id_aa_ets_signerAttr; + public static final DERObjectIdentifier otherSigCert = PKCSObjectIdentifiers.id_aa_ets_otherSigCert; + public static final DERObjectIdentifier contentTimestamp = PKCSObjectIdentifiers.id_aa_ets_contentTimestamp; + public static final DERObjectIdentifier certificateRefs = PKCSObjectIdentifiers.id_aa_ets_certificateRefs; + public static final DERObjectIdentifier revocationRefs = PKCSObjectIdentifiers.id_aa_ets_revocationRefs; + public static final DERObjectIdentifier certValues = PKCSObjectIdentifiers.id_aa_ets_certValues; + public static final DERObjectIdentifier revocationValues = PKCSObjectIdentifiers.id_aa_ets_revocationValues; + public static final DERObjectIdentifier escTimeStamp = PKCSObjectIdentifiers.id_aa_ets_escTimeStamp; + public static final DERObjectIdentifier certCRLTimestamp = PKCSObjectIdentifiers.id_aa_ets_certCRLTimestamp; + public static final DERObjectIdentifier archiveTimestamp = PKCSObjectIdentifiers.id_aa_ets_archiveTimestamp; +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/esf/OtherHashAlgAndValue.java b/src/com/google/bitcoin/bouncycastle/asn1/esf/OtherHashAlgAndValue.java new file mode 100644 index 000000000..83508b495 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/esf/OtherHashAlgAndValue.java @@ -0,0 +1,78 @@ +package com.google.bitcoin.bouncycastle.asn1.esf; + +import com.google.bitcoin.bouncycastle.asn1.*; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class OtherHashAlgAndValue + extends ASN1Encodable +{ + private AlgorithmIdentifier hashAlgorithm; + private ASN1OctetString hashValue; + + + public static OtherHashAlgAndValue getInstance( + Object obj) + { + if (obj == null || obj instanceof OtherHashAlgAndValue) + { + return (OtherHashAlgAndValue) obj; + } + else if (obj instanceof ASN1Sequence) + { + return new OtherHashAlgAndValue((ASN1Sequence) obj); + } + + throw new IllegalArgumentException( + "unknown object in 'OtherHashAlgAndValue' factory : " + + obj.getClass().getName() + "."); + } + + public OtherHashAlgAndValue( + ASN1Sequence seq) + { + if (seq.size() != 2) + { + throw new IllegalArgumentException("Bad sequence size: " + seq.size()); + } + + hashAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(0)); + hashValue = ASN1OctetString.getInstance(seq.getObjectAt(1)); + } + + public OtherHashAlgAndValue( + AlgorithmIdentifier hashAlgorithm, + ASN1OctetString hashValue) + { + this.hashAlgorithm = hashAlgorithm; + this.hashValue = hashValue; + } + + public AlgorithmIdentifier getHashAlgorithm() + { + return hashAlgorithm; + } + + public ASN1OctetString getHashValue() + { + return hashValue; + } + + /** + *
+     * OtherHashAlgAndValue ::= SEQUENCE {
+     *     hashAlgorithm AlgorithmIdentifier,
+     *     hashValue OtherHashValue }
+     *
+     * OtherHashValue ::= OCTET STRING
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(hashAlgorithm); + v.add(hashValue); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/esf/SPUserNotice.java b/src/com/google/bitcoin/bouncycastle/asn1/esf/SPUserNotice.java new file mode 100644 index 000000000..ebe56b132 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/esf/SPUserNotice.java @@ -0,0 +1,94 @@ +package com.google.bitcoin.bouncycastle.asn1.esf; + +import com.google.bitcoin.bouncycastle.asn1.*; +import com.google.bitcoin.bouncycastle.asn1.x509.DisplayText; +import com.google.bitcoin.bouncycastle.asn1.x509.NoticeReference; + +import java.util.Enumeration; + +public class SPUserNotice +{ + private NoticeReference noticeRef; + private DisplayText explicitText; + + public static SPUserNotice getInstance( + Object obj) + { + if (obj == null || obj instanceof SPUserNotice) + { + return (SPUserNotice) obj; + } + else if (obj instanceof ASN1Sequence) + { + return new SPUserNotice((ASN1Sequence) obj); + } + + throw new IllegalArgumentException( + "unknown object in 'SPUserNotice' factory : " + + obj.getClass().getName() + "."); + } + + public SPUserNotice( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + while (e.hasMoreElements()) + { + DEREncodable object = (DEREncodable) e.nextElement(); + if (object instanceof NoticeReference) + { + noticeRef = NoticeReference.getInstance(object); + } + else if (object instanceof DisplayText) + { + explicitText = DisplayText.getInstance(object); + } + else + { + throw new IllegalArgumentException("Invalid element in 'SPUserNotice'."); + } + } + } + + public SPUserNotice( + NoticeReference noticeRef, + DisplayText explicitText) + { + this.noticeRef = noticeRef; + this.explicitText = explicitText; + } + + public NoticeReference getNoticeRef() + { + return noticeRef; + } + + public DisplayText getExplicitText() + { + return explicitText; + } + + /** + *
+     * SPUserNotice ::= SEQUENCE {
+     *     noticeRef NoticeReference OPTIONAL,
+     *     explicitText DisplayText OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (noticeRef != null) + { + v.add(noticeRef); + } + + if (explicitText != null) + { + v.add(explicitText); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/esf/SPuri.java b/src/com/google/bitcoin/bouncycastle/asn1/esf/SPuri.java new file mode 100644 index 000000000..8fdd3a7c1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/esf/SPuri.java @@ -0,0 +1,47 @@ +package com.google.bitcoin.bouncycastle.asn1.esf; + +import com.google.bitcoin.bouncycastle.asn1.DERIA5String; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class SPuri +{ + private DERIA5String uri; + + public static SPuri getInstance( + Object obj) + { + if (obj instanceof SPuri) + { + return (SPuri) obj; + } + else if (obj instanceof DERIA5String) + { + return new SPuri((DERIA5String) obj); + } + + throw new IllegalArgumentException( + "unknown object in 'SPuri' factory: " + + obj.getClass().getName() + "."); + } + + public SPuri( + DERIA5String uri) + { + this.uri = uri; + } + + public DERIA5String getUri() + { + return uri; + } + + /** + *
+     * SPuri ::= IA5String
+     * 
+ */ + public DERObject toASN1Object() + { + return uri.getDERObject(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/esf/SigPolicyQualifierInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/esf/SigPolicyQualifierInfo.java new file mode 100644 index 000000000..4bbe06d55 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/esf/SigPolicyQualifierInfo.java @@ -0,0 +1,71 @@ +package com.google.bitcoin.bouncycastle.asn1.esf; + +import com.google.bitcoin.bouncycastle.asn1.*; + +public class SigPolicyQualifierInfo + extends ASN1Encodable +{ + private DERObjectIdentifier sigPolicyQualifierId; + private DEREncodable sigQualifier; + + public SigPolicyQualifierInfo( + DERObjectIdentifier sigPolicyQualifierId, + DEREncodable sigQualifier) + { + this.sigPolicyQualifierId = sigPolicyQualifierId; + this.sigQualifier = sigQualifier; + } + + public SigPolicyQualifierInfo( + ASN1Sequence seq) + { + sigPolicyQualifierId = DERObjectIdentifier.getInstance(seq.getObjectAt(0)); + sigQualifier = seq.getObjectAt(1); + } + + public static SigPolicyQualifierInfo getInstance( + Object obj) + { + if (obj == null || obj instanceof SigPolicyQualifierInfo) + { + return (SigPolicyQualifierInfo) obj; + } + else if (obj instanceof ASN1Sequence) + { + return new SigPolicyQualifierInfo((ASN1Sequence) obj); + } + + throw new IllegalArgumentException( + "unknown object in 'SigPolicyQualifierInfo' factory: " + + obj.getClass().getName() + "."); + } + + public DERObjectIdentifier getSigPolicyQualifierId() + { + return sigPolicyQualifierId; + } + + public DEREncodable getSigQualifier() + { + return sigQualifier; + } + + /** + *
+     * SigPolicyQualifierInfo ::= SEQUENCE {
+     *    sigPolicyQualifierId SigPolicyQualifierId,
+     *    sigQualifier ANY DEFINED BY sigPolicyQualifierId }
+     *
+     * SigPolicyQualifierId ::= OBJECT IDENTIFIER
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(sigPolicyQualifierId); + v.add(sigQualifier); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/esf/SigPolicyQualifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/esf/SigPolicyQualifiers.java new file mode 100644 index 000000000..898f5c7b0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/esf/SigPolicyQualifiers.java @@ -0,0 +1,75 @@ +package com.google.bitcoin.bouncycastle.asn1.esf; + +import com.google.bitcoin.bouncycastle.asn1.*; + +public class SigPolicyQualifiers + extends ASN1Encodable +{ + ASN1Sequence qualifiers; + + public static SigPolicyQualifiers getInstance( + Object obj) + { + if (obj instanceof SigPolicyQualifiers) + { + return (SigPolicyQualifiers) obj; + } + else if (obj instanceof ASN1Sequence) + { + return new SigPolicyQualifiers((ASN1Sequence) obj); + } + + throw new IllegalArgumentException( + "unknown object in 'SigPolicyQualifiers' factory: " + + obj.getClass().getName() + "."); + } + + public SigPolicyQualifiers( + ASN1Sequence seq) + { + qualifiers = seq; + } + + public SigPolicyQualifiers( + SigPolicyQualifierInfo[] qualifierInfos) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + for (int i=0; i < qualifierInfos.length; i++) + { + v.add(qualifierInfos[i]); + } + qualifiers = new DERSequence(v); + } + + /** + * Return the number of qualifier info elements present. + * + * @return number of elements present. + */ + public int size() + { + return qualifiers.size(); + } + + /** + * Return the SigPolicyQualifierInfo at index i. + * + * @param i index of the string of interest + * @return the string at index i. + */ + public SigPolicyQualifierInfo getStringAt( + int i) + { + return SigPolicyQualifierInfo.getInstance(qualifiers.getObjectAt(i)); + } + + /** + *
+     * SigPolicyQualifiers ::= SEQUENCE SIZE (1..MAX) OF SigPolicyQualifierInfo
+     * 
+ */ + public DERObject toASN1Object() + { + return qualifiers; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/esf/SignaturePolicyId.java b/src/com/google/bitcoin/bouncycastle/asn1/esf/SignaturePolicyId.java new file mode 100644 index 000000000..f1feeec9a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/esf/SignaturePolicyId.java @@ -0,0 +1,100 @@ +package com.google.bitcoin.bouncycastle.asn1.esf; + +import com.google.bitcoin.bouncycastle.asn1.*; + +public class SignaturePolicyId + extends ASN1Encodable +{ + private DERObjectIdentifier sigPolicyId; + private OtherHashAlgAndValue sigPolicyHash; + private SigPolicyQualifiers sigPolicyQualifiers; + + + public static SignaturePolicyId getInstance( + Object obj) + { + if (obj == null || obj instanceof SignaturePolicyId) + { + return (SignaturePolicyId) obj; + } + else if (obj instanceof ASN1Sequence) + { + return new SignaturePolicyId((ASN1Sequence) obj); + } + + throw new IllegalArgumentException( + "Unknown object in 'SignaturePolicyId' factory : " + + obj.getClass().getName() + "."); + } + + public SignaturePolicyId( + ASN1Sequence seq) + { + if (seq.size() != 2 && seq.size() != 3) + { + throw new IllegalArgumentException("Bad sequence size: " + seq.size()); + } + + sigPolicyId = DERObjectIdentifier.getInstance(seq.getObjectAt(0)); + sigPolicyHash = OtherHashAlgAndValue.getInstance(seq.getObjectAt(1)); + + if (seq.size() == 3) + { + sigPolicyQualifiers = SigPolicyQualifiers.getInstance(seq.getObjectAt(2)); + } + } + + public SignaturePolicyId( + DERObjectIdentifier sigPolicyIdentifier, + OtherHashAlgAndValue sigPolicyHash) + { + this(sigPolicyIdentifier, sigPolicyHash, null); + } + + public SignaturePolicyId( + DERObjectIdentifier sigPolicyId, + OtherHashAlgAndValue sigPolicyHash, + SigPolicyQualifiers sigPolicyQualifiers) + { + this.sigPolicyId = sigPolicyId; + this.sigPolicyHash = sigPolicyHash; + this.sigPolicyQualifiers = sigPolicyQualifiers; + } + + public DERObjectIdentifier getSigPolicyId() + { + return sigPolicyId; + } + + public OtherHashAlgAndValue getSigPolicyHash() + { + return sigPolicyHash; + } + + public SigPolicyQualifiers getSigPolicyQualifiers() + { + return sigPolicyQualifiers; + } + + /** + *
+     * SignaturePolicyId ::= SEQUENCE {
+     *     sigPolicyId SigPolicyId,
+     *     sigPolicyHash SigPolicyHash,
+     *     sigPolicyQualifiers SEQUENCE SIZE (1..MAX) OF SigPolicyQualifierInfo OPTIONAL}
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(sigPolicyId); + v.add(sigPolicyHash); + if (sigPolicyQualifiers != null) + { + v.add(sigPolicyQualifiers); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/esf/SignaturePolicyIdentifier.java b/src/com/google/bitcoin/bouncycastle/asn1/esf/SignaturePolicyIdentifier.java new file mode 100644 index 000000000..5641250ba --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/esf/SignaturePolicyIdentifier.java @@ -0,0 +1,74 @@ +package com.google.bitcoin.bouncycastle.asn1.esf; + +import com.google.bitcoin.bouncycastle.asn1.*; + +public class SignaturePolicyIdentifier + extends ASN1Encodable +{ + private SignaturePolicyId signaturePolicyId; + private boolean isSignaturePolicyImplied; + + public static SignaturePolicyIdentifier getInstance( + Object obj) + { + if (obj == null || obj instanceof SignaturePolicyIdentifier) + { + return (SignaturePolicyIdentifier) obj; + } + else if (obj instanceof ASN1Sequence) + { + return new SignaturePolicyIdentifier(SignaturePolicyId.getInstance(obj)); + } + else if (obj instanceof ASN1Null) + { + return new SignaturePolicyIdentifier(); + } + + throw new IllegalArgumentException( + "unknown object in 'SignaturePolicyIdentifier' factory: " + + obj.getClass().getName() + "."); + } + + public SignaturePolicyIdentifier() + { + this.isSignaturePolicyImplied = true; + } + + public SignaturePolicyIdentifier( + SignaturePolicyId signaturePolicyId) + { + this.signaturePolicyId = signaturePolicyId; + this.isSignaturePolicyImplied = false; + } + + public SignaturePolicyId getSignaturePolicyId() + { + return signaturePolicyId; + } + + public boolean isSignaturePolicyImplied() + { + return isSignaturePolicyImplied; + } + + /** + *
+     * SignaturePolicyIdentifier ::= CHOICE{
+     *     SignaturePolicyId         SignaturePolicyId,
+     *     SignaturePolicyImplied    SignaturePolicyImplied }
+     *
+     * SignaturePolicyImplied ::= NULL
+     * 
+ */ + public DERObject toASN1Object() + { + if (isSignaturePolicyImplied) + { + return new DERNull(); + } + else + { + return signaturePolicyId.getDERObject(); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/esf/SignerAttribute.java b/src/com/google/bitcoin/bouncycastle/asn1/esf/SignerAttribute.java new file mode 100644 index 000000000..e6033bcf1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/esf/SignerAttribute.java @@ -0,0 +1,97 @@ +package com.google.bitcoin.bouncycastle.asn1.esf; + +import com.google.bitcoin.bouncycastle.asn1.*; +import com.google.bitcoin.bouncycastle.asn1.x509.AttributeCertificate; + + +public class SignerAttribute + extends ASN1Encodable +{ + private ASN1Sequence claimedAttributes; + private AttributeCertificate certifiedAttributes; + + public static SignerAttribute getInstance( + Object o) + { + if (o == null || o instanceof SignerAttribute) + { + return (SignerAttribute) o; + } + else if (o instanceof ASN1Sequence) + { + return new SignerAttribute(o); + } + + throw new IllegalArgumentException( + "unknown object in 'SignerAttribute' factory: " + + o.getClass().getName() + "."); + } + + private SignerAttribute( + Object o) + { + ASN1Sequence seq = (ASN1Sequence) o; + DERTaggedObject taggedObject = (DERTaggedObject) seq.getObjectAt(0); + if (taggedObject.getTagNo() == 0) + { + claimedAttributes = ASN1Sequence.getInstance(taggedObject, true); + } + else if (taggedObject.getTagNo() == 1) + { + certifiedAttributes = AttributeCertificate.getInstance(taggedObject); + } + else + { + throw new IllegalArgumentException("illegal tag."); + } + } + + public SignerAttribute( + ASN1Sequence claimedAttributes) + { + this.claimedAttributes = claimedAttributes; + } + + public SignerAttribute( + AttributeCertificate certifiedAttributes) + { + this.certifiedAttributes = certifiedAttributes; + } + + public ASN1Sequence getClaimedAttributes() + { + return claimedAttributes; + } + + public AttributeCertificate getCertifiedAttributes() + { + return certifiedAttributes; + } + + /** + * + *
+     *  SignerAttribute ::= SEQUENCE OF CHOICE {
+     *      claimedAttributes   [0] ClaimedAttributes,
+     *      certifiedAttributes [1] CertifiedAttributes }
+     *
+     *  ClaimedAttributes ::= SEQUENCE OF Attribute
+     *  CertifiedAttributes ::= AttributeCertificate -- as defined in RFC 3281: see clause 4.1.
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (claimedAttributes != null) + { + v.add(new DERTaggedObject(0, claimedAttributes)); + } + else + { + v.add(new DERTaggedObject(1, certifiedAttributes)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/esf/SignerLocation.java b/src/com/google/bitcoin/bouncycastle/asn1/esf/SignerLocation.java new file mode 100644 index 000000000..6e676bd0a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/esf/SignerLocation.java @@ -0,0 +1,159 @@ +package com.google.bitcoin.bouncycastle.asn1.esf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERUTF8String; + +import java.util.Enumeration; + +/** + * Signer-Location attribute (RFC3126). + * + *
+ *   SignerLocation ::= SEQUENCE {
+ *       countryName        [0] DirectoryString OPTIONAL,
+ *       localityName       [1] DirectoryString OPTIONAL,
+ *       postalAddress      [2] PostalAddress OPTIONAL }
+ *
+ *   PostalAddress ::= SEQUENCE SIZE(1..6) OF DirectoryString
+ * 
+ */ +public class SignerLocation + extends ASN1Encodable +{ + private DERUTF8String countryName; + private DERUTF8String localityName; + private ASN1Sequence postalAddress; + + public SignerLocation( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + while (e.hasMoreElements()) + { + DERTaggedObject o = (DERTaggedObject)e.nextElement(); + + switch (o.getTagNo()) + { + case 0: + this.countryName = DERUTF8String.getInstance(o, true); + break; + case 1: + this.localityName = DERUTF8String.getInstance(o, true); + break; + case 2: + if (o.isExplicit()) + { + this.postalAddress = ASN1Sequence.getInstance(o, true); + } + else // handle erroneous implicitly tagged sequences + { + this.postalAddress = ASN1Sequence.getInstance(o, false); + } + if (postalAddress != null && postalAddress.size() > 6) + { + throw new IllegalArgumentException("postal address must contain less than 6 strings"); + } + break; + default: + throw new IllegalArgumentException("illegal tag"); + } + } + } + + public SignerLocation( + DERUTF8String countryName, + DERUTF8String localityName, + ASN1Sequence postalAddress) + { + if (postalAddress != null && postalAddress.size() > 6) + { + throw new IllegalArgumentException("postal address must contain less than 6 strings"); + } + + if (countryName != null) + { + this.countryName = DERUTF8String.getInstance(countryName.toASN1Object()); + } + + if (localityName != null) + { + this.localityName = DERUTF8String.getInstance(localityName.toASN1Object()); + } + + if (postalAddress != null) + { + this.postalAddress = ASN1Sequence.getInstance(postalAddress.toASN1Object()); + } + } + + public static SignerLocation getInstance( + Object obj) + { + if (obj == null || obj instanceof SignerLocation) + { + return (SignerLocation)obj; + } + + return new SignerLocation(ASN1Sequence.getInstance(obj)); + } + + public DERUTF8String getCountryName() + { + return countryName; + } + + public DERUTF8String getLocalityName() + { + return localityName; + } + + public ASN1Sequence getPostalAddress() + { + return postalAddress; + } + + /** + *
+     *   SignerLocation ::= SEQUENCE {
+     *       countryName        [0] DirectoryString OPTIONAL,
+     *       localityName       [1] DirectoryString OPTIONAL,
+     *       postalAddress      [2] PostalAddress OPTIONAL }
+     *
+     *   PostalAddress ::= SEQUENCE SIZE(1..6) OF DirectoryString
+     *   
+     *   DirectoryString ::= CHOICE {
+     *         teletexString           TeletexString (SIZE (1..MAX)),
+     *         printableString         PrintableString (SIZE (1..MAX)),
+     *         universalString         UniversalString (SIZE (1..MAX)),
+     *         utf8String              UTF8String (SIZE (1.. MAX)),
+     *         bmpString               BMPString (SIZE (1..MAX)) }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (countryName != null) + { + v.add(new DERTaggedObject(true, 0, countryName)); + } + + if (localityName != null) + { + v.add(new DERTaggedObject(true, 1, localityName)); + } + + if (postalAddress != null) + { + v.add(new DERTaggedObject(true, 2, postalAddress)); + } + + return new DERSequence(v); + } +} \ No newline at end of file diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ess/ContentHints.java b/src/com/google/bitcoin/bouncycastle/asn1/ess/ContentHints.java new file mode 100644 index 000000000..6a2e73dc9 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ess/ContentHints.java @@ -0,0 +1,96 @@ +package com.google.bitcoin.bouncycastle.asn1.ess; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERUTF8String; + +public class ContentHints + extends ASN1Encodable +{ + private DERUTF8String contentDescription; + private DERObjectIdentifier contentType; + + public static ContentHints getInstance(Object o) + { + if (o == null || o instanceof ContentHints) + { + return (ContentHints)o; + } + else if (o instanceof ASN1Sequence) + { + return new ContentHints((ASN1Sequence)o); + } + + throw new IllegalArgumentException( + "unknown object in 'ContentHints' factory : " + + o.getClass().getName() + "."); + } + + /** + * constructor + */ + private ContentHints(ASN1Sequence seq) + { + DEREncodable field = seq.getObjectAt(0); + if (field.getDERObject() instanceof DERUTF8String) + { + contentDescription = DERUTF8String.getInstance(field); + contentType = DERObjectIdentifier.getInstance(seq.getObjectAt(1)); + } + else + { + contentType = DERObjectIdentifier.getInstance(seq.getObjectAt(0)); + } + } + + public ContentHints( + DERObjectIdentifier contentType) + { + this.contentType = contentType; + this.contentDescription = null; + } + + public ContentHints( + DERObjectIdentifier contentType, + DERUTF8String contentDescription) + { + this.contentType = contentType; + this.contentDescription = contentDescription; + } + + public DERObjectIdentifier getContentType() + { + return contentType; + } + + public DERUTF8String getContentDescription() + { + return contentDescription; + } + + /** + *
+     * ContentHints ::= SEQUENCE {
+     *   contentDescription UTF8String (SIZE (1..MAX)) OPTIONAL,
+     *   contentType ContentType }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (contentDescription != null) + { + v.add(contentDescription); + } + + v.add(contentType); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ess/ContentIdentifier.java b/src/com/google/bitcoin/bouncycastle/asn1/ess/ContentIdentifier.java new file mode 100644 index 000000000..514c6f9b1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ess/ContentIdentifier.java @@ -0,0 +1,65 @@ +package com.google.bitcoin.bouncycastle.asn1.ess; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; + +public class ContentIdentifier + extends ASN1Encodable +{ + ASN1OctetString value; + + public static ContentIdentifier getInstance(Object o) + { + if (o == null || o instanceof ContentIdentifier) + { + return (ContentIdentifier) o; + } + else if (o instanceof ASN1OctetString) + { + return new ContentIdentifier((ASN1OctetString) o); + } + + throw new IllegalArgumentException( + "unknown object in 'ContentIdentifier' factory : " + + o.getClass().getName() + "."); + } + + /** + * Create from OCTET STRING whose octets represent the identifier. + */ + public ContentIdentifier( + ASN1OctetString value) + { + this.value = value; + } + + /** + * Create from byte array representing the identifier. + */ + public ContentIdentifier( + byte[] value) + { + this(new DEROctetString(value)); + } + + public ASN1OctetString getValue() + { + return value; + } + + /** + * The definition of ContentIdentifier is + *
+     * ContentIdentifier ::=  OCTET STRING
+     * 
+ * id-aa-contentIdentifier OBJECT IDENTIFIER ::= { iso(1) + * member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) + * smime(16) id-aa(2) 7 } + */ + public DERObject toASN1Object() + { + return value; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ess/ESSCertID.java b/src/com/google/bitcoin/bouncycastle/asn1/ess/ESSCertID.java new file mode 100644 index 000000000..86e8ddef3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ess/ESSCertID.java @@ -0,0 +1,97 @@ +package com.google.bitcoin.bouncycastle.asn1.ess; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.IssuerSerial; + +public class ESSCertID + extends ASN1Encodable +{ + private ASN1OctetString certHash; + + private IssuerSerial issuerSerial; + + public static ESSCertID getInstance(Object o) + { + if (o == null || o instanceof ESSCertID) + { + return (ESSCertID)o; + } + else if (o instanceof ASN1Sequence) + { + return new ESSCertID((ASN1Sequence)o); + } + + throw new IllegalArgumentException( + "unknown object in 'ESSCertID' factory : " + + o.getClass().getName() + "."); + } + + /** + * constructor + */ + public ESSCertID(ASN1Sequence seq) + { + if (seq.size() < 1 || seq.size() > 2) + { + throw new IllegalArgumentException("Bad sequence size: " + seq.size()); + } + + certHash = ASN1OctetString.getInstance(seq.getObjectAt(0)); + + if (seq.size() > 1) + { + issuerSerial = IssuerSerial.getInstance(seq.getObjectAt(1)); + } + } + + public ESSCertID( + byte[] hash) + { + certHash = new DEROctetString(hash); + } + + public ESSCertID( + byte[] hash, + IssuerSerial issuerSerial) + { + this.certHash = new DEROctetString(hash); + this.issuerSerial = issuerSerial; + } + + public byte[] getCertHash() + { + return certHash.getOctets(); + } + + public IssuerSerial getIssuerSerial() + { + return issuerSerial; + } + + /** + *
+     * ESSCertID ::= SEQUENCE {
+     *     certHash Hash, 
+     *     issuerSerial IssuerSerial OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(certHash); + + if (issuerSerial != null) + { + v.add(issuerSerial); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ess/ESSCertIDv2.java b/src/com/google/bitcoin/bouncycastle/asn1/ess/ESSCertIDv2.java new file mode 100644 index 000000000..ef94a147e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ess/ESSCertIDv2.java @@ -0,0 +1,138 @@ +package com.google.bitcoin.bouncycastle.asn1.ess; + +import com.google.bitcoin.bouncycastle.asn1.*; +import com.google.bitcoin.bouncycastle.asn1.nist.NISTObjectIdentifiers; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; +import com.google.bitcoin.bouncycastle.asn1.x509.IssuerSerial; + +public class ESSCertIDv2 + extends ASN1Encodable +{ + private AlgorithmIdentifier hashAlgorithm; + private byte[] certHash; + private IssuerSerial issuerSerial; + private static final AlgorithmIdentifier DEFAULT_ALG_ID = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256); + + public static ESSCertIDv2 getInstance( + Object o) + { + if (o == null || o instanceof ESSCertIDv2) + { + return (ESSCertIDv2) o; + } + else if (o instanceof ASN1Sequence) + { + return new ESSCertIDv2((ASN1Sequence) o); + } + + throw new IllegalArgumentException( + "unknown object in 'ESSCertIDv2' factory : " + + o.getClass().getName() + "."); + } + + public ESSCertIDv2( + ASN1Sequence seq) + { + if (seq.size() != 2 && seq.size() != 3) + { + throw new IllegalArgumentException("Bad sequence size: " + seq.size()); + } + + int count = 0; + + if (seq.getObjectAt(0) instanceof ASN1OctetString) + { + // Default value + this.hashAlgorithm = DEFAULT_ALG_ID; + } + else + { + this.hashAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(count++).getDERObject()); + } + + this.certHash = ASN1OctetString.getInstance(seq.getObjectAt(count++).getDERObject()).getOctets(); + + if (seq.size() > count) + { + this.issuerSerial = new IssuerSerial(ASN1Sequence.getInstance(seq.getObjectAt(count).getDERObject())); + } + } + + public ESSCertIDv2( + AlgorithmIdentifier algId, + byte[] certHash) + { + this(algId, certHash, null); + } + + public ESSCertIDv2( + AlgorithmIdentifier algId, + byte[] certHash, + IssuerSerial issuerSerial) + { + if (algId == null) + { + // Default value + this.hashAlgorithm = DEFAULT_ALG_ID; + } + else + { + this.hashAlgorithm = algId; + } + + this.certHash = certHash; + this.issuerSerial = issuerSerial; + } + + public AlgorithmIdentifier getHashAlgorithm() + { + return this.hashAlgorithm; + } + + public byte[] getCertHash() + { + return certHash; + } + + public IssuerSerial getIssuerSerial() + { + return issuerSerial; + } + + /** + *
+     * ESSCertIDv2 ::=  SEQUENCE {
+     *     hashAlgorithm     AlgorithmIdentifier
+     *              DEFAULT {algorithm id-sha256},
+     *     certHash          Hash,
+     *     issuerSerial      IssuerSerial OPTIONAL
+     * }
+     *
+     * Hash ::= OCTET STRING
+     *
+     * IssuerSerial ::= SEQUENCE {
+     *     issuer         GeneralNames,
+     *     serialNumber   CertificateSerialNumber
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (!hashAlgorithm.equals(DEFAULT_ALG_ID)) + { + v.add(hashAlgorithm); + } + + v.add(new DEROctetString(certHash).toASN1Object()); + + if (issuerSerial != null) + { + v.add(issuerSerial); + } + + return new DERSequence(v); + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ess/OtherCertID.java b/src/com/google/bitcoin/bouncycastle/asn1/ess/OtherCertID.java new file mode 100644 index 000000000..2d28f90a4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ess/OtherCertID.java @@ -0,0 +1,138 @@ +package com.google.bitcoin.bouncycastle.asn1.ess; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; +import com.google.bitcoin.bouncycastle.asn1.x509.DigestInfo; +import com.google.bitcoin.bouncycastle.asn1.x509.IssuerSerial; + +public class OtherCertID + extends ASN1Encodable +{ + private ASN1Encodable otherCertHash; + private IssuerSerial issuerSerial; + + public static OtherCertID getInstance(Object o) + { + if (o == null || o instanceof OtherCertID) + { + return (OtherCertID) o; + } + else if (o instanceof ASN1Sequence) + { + return new OtherCertID((ASN1Sequence) o); + } + + throw new IllegalArgumentException( + "unknown object in 'OtherCertID' factory : " + + o.getClass().getName() + "."); + } + + /** + * constructor + */ + public OtherCertID(ASN1Sequence seq) + { + if (seq.size() < 1 || seq.size() > 2) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + if (seq.getObjectAt(0).getDERObject() instanceof ASN1OctetString) + { + otherCertHash = ASN1OctetString.getInstance(seq.getObjectAt(0)); + } + else + { + otherCertHash = DigestInfo.getInstance(seq.getObjectAt(0)); + + } + + if (seq.size() > 1) + { + issuerSerial = new IssuerSerial(ASN1Sequence.getInstance(seq.getObjectAt(1))); + } + } + + public OtherCertID( + AlgorithmIdentifier algId, + byte[] digest) + { + this.otherCertHash = new DigestInfo(algId, digest); + } + + public OtherCertID( + AlgorithmIdentifier algId, + byte[] digest, + IssuerSerial issuerSerial) + { + this.otherCertHash = new DigestInfo(algId, digest); + this.issuerSerial = issuerSerial; + } + + public AlgorithmIdentifier getAlgorithmHash() + { + if (otherCertHash.getDERObject() instanceof ASN1OctetString) + { + // SHA-1 + return new AlgorithmIdentifier("1.3.14.3.2.26"); + } + else + { + return DigestInfo.getInstance(otherCertHash).getAlgorithmId(); + } + } + + public byte[] getCertHash() + { + if (otherCertHash.getDERObject() instanceof ASN1OctetString) + { + // SHA-1 + return ((ASN1OctetString)otherCertHash.getDERObject()).getOctets(); + } + else + { + return DigestInfo.getInstance(otherCertHash).getDigest(); + } + } + + public IssuerSerial getIssuerSerial() + { + return issuerSerial; + } + + /** + *
+     * OtherCertID ::= SEQUENCE {
+     *     otherCertHash    OtherHash,
+     *     issuerSerial     IssuerSerial OPTIONAL }
+     *
+     * OtherHash ::= CHOICE {
+     *     sha1Hash     OCTET STRING,
+     *     otherHash    OtherHashAlgAndValue }
+     *
+     * OtherHashAlgAndValue ::= SEQUENCE {
+     *     hashAlgorithm    AlgorithmIdentifier,
+     *     hashValue        OCTET STRING }
+     *
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(otherCertHash); + + if (issuerSerial != null) + { + v.add(issuerSerial); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ess/OtherSigningCertificate.java b/src/com/google/bitcoin/bouncycastle/asn1/ess/OtherSigningCertificate.java new file mode 100644 index 000000000..231e19403 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ess/OtherSigningCertificate.java @@ -0,0 +1,111 @@ +package com.google.bitcoin.bouncycastle.asn1.ess; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.PolicyInformation; + +public class OtherSigningCertificate + extends ASN1Encodable +{ + ASN1Sequence certs; + ASN1Sequence policies; + + public static OtherSigningCertificate getInstance(Object o) + { + if (o == null || o instanceof OtherSigningCertificate) + { + return (OtherSigningCertificate) o; + } + else if (o instanceof ASN1Sequence) + { + return new OtherSigningCertificate((ASN1Sequence) o); + } + + throw new IllegalArgumentException( + "unknown object in 'OtherSigningCertificate' factory : " + + o.getClass().getName() + "."); + } + + /** + * constructeurs + */ + public OtherSigningCertificate(ASN1Sequence seq) + { + if (seq.size() < 1 || seq.size() > 2) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + this.certs = ASN1Sequence.getInstance(seq.getObjectAt(0)); + + if (seq.size() > 1) + { + this.policies = ASN1Sequence.getInstance(seq.getObjectAt(1)); + } + } + + public OtherSigningCertificate( + OtherCertID otherCertID) + { + certs = new DERSequence(otherCertID); + } + + public OtherCertID[] getCerts() + { + OtherCertID[] cs = new OtherCertID[certs.size()]; + + for (int i = 0; i != certs.size(); i++) + { + cs[i] = OtherCertID.getInstance(certs.getObjectAt(i)); + } + + return cs; + } + + public PolicyInformation[] getPolicies() + { + if (policies == null) + { + return null; + } + + PolicyInformation[] ps = new PolicyInformation[policies.size()]; + + for (int i = 0; i != policies.size(); i++) + { + ps[i] = PolicyInformation.getInstance(policies.getObjectAt(i)); + } + + return ps; + } + + /** + * The definition of OtherSigningCertificate is + *
+     * OtherSigningCertificate ::=  SEQUENCE {
+     *      certs        SEQUENCE OF OtherCertID,
+     *      policies     SEQUENCE OF PolicyInformation OPTIONAL
+     * }
+     * 
+ * id-aa-ets-otherSigCert OBJECT IDENTIFIER ::= { iso(1) + * member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) + * smime(16) id-aa(2) 19 } + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(certs); + + if (policies != null) + { + v.add(policies); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ess/SigningCertificate.java b/src/com/google/bitcoin/bouncycastle/asn1/ess/SigningCertificate.java new file mode 100644 index 000000000..22316e04c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ess/SigningCertificate.java @@ -0,0 +1,111 @@ +package com.google.bitcoin.bouncycastle.asn1.ess; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.PolicyInformation; + + +public class SigningCertificate + extends ASN1Encodable +{ + ASN1Sequence certs; + ASN1Sequence policies; + + public static SigningCertificate getInstance(Object o) + { + if (o == null || o instanceof SigningCertificate) + { + return (SigningCertificate) o; + } + else if (o instanceof ASN1Sequence) + { + return new SigningCertificate((ASN1Sequence) o); + } + + throw new IllegalArgumentException( + "unknown object in 'SigningCertificate' factory : " + + o.getClass().getName() + "."); + } + + /** + * constructeurs + */ + public SigningCertificate(ASN1Sequence seq) + { + if (seq.size() < 1 || seq.size() > 2) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + this.certs = ASN1Sequence.getInstance(seq.getObjectAt(0)); + + if (seq.size() > 1) + { + this.policies = ASN1Sequence.getInstance(seq.getObjectAt(1)); + } + } + + public SigningCertificate( + ESSCertID essCertID) + { + certs = new DERSequence(essCertID); + } + + public ESSCertID[] getCerts() + { + ESSCertID[] cs = new ESSCertID[certs.size()]; + + for (int i = 0; i != certs.size(); i++) + { + cs[i] = ESSCertID.getInstance(certs.getObjectAt(i)); + } + + return cs; + } + + public PolicyInformation[] getPolicies() + { + if (policies == null) + { + return null; + } + + PolicyInformation[] ps = new PolicyInformation[policies.size()]; + + for (int i = 0; i != policies.size(); i++) + { + ps[i] = PolicyInformation.getInstance(policies.getObjectAt(i)); + } + + return ps; + } + + /** + * The definition of SigningCertificate is + *
+     * SigningCertificate ::=  SEQUENCE {
+     *      certs        SEQUENCE OF ESSCertID,
+     *      policies     SEQUENCE OF PolicyInformation OPTIONAL
+     * }
+     * 
+ * id-aa-signingCertificate OBJECT IDENTIFIER ::= { iso(1) + * member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) + * smime(16) id-aa(2) 12 } + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(certs); + + if (policies != null) + { + v.add(policies); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ess/SigningCertificateV2.java b/src/com/google/bitcoin/bouncycastle/asn1/ess/SigningCertificateV2.java new file mode 100644 index 000000000..0992e0a8c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ess/SigningCertificateV2.java @@ -0,0 +1,132 @@ +package com.google.bitcoin.bouncycastle.asn1.ess; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.PolicyInformation; + +public class SigningCertificateV2 + extends ASN1Encodable +{ + ASN1Sequence certs; + ASN1Sequence policies; + + public static SigningCertificateV2 getInstance( + Object o) + { + if (o == null || o instanceof SigningCertificateV2) + { + return (SigningCertificateV2) o; + } + else if (o instanceof ASN1Sequence) + { + return new SigningCertificateV2((ASN1Sequence) o); + } + + throw new IllegalArgumentException( + "unknown object in 'SigningCertificateV2' factory : " + + o.getClass().getName() + "."); + } + + public SigningCertificateV2( + ASN1Sequence seq) + { + if (seq.size() < 1 || seq.size() > 2) + { + throw new IllegalArgumentException("Bad sequence size: " + seq.size()); + } + + this.certs = ASN1Sequence.getInstance(seq.getObjectAt(0)); + + if (seq.size() > 1) + { + this.policies = ASN1Sequence.getInstance(seq.getObjectAt(1)); + } + } + + public SigningCertificateV2( + ESSCertIDv2[] certs) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + for (int i=0; i < certs.length; i++) + { + v.add(certs[i]); + } + this.certs = new DERSequence(v); + } + + public SigningCertificateV2( + ESSCertIDv2[] certs, + PolicyInformation[] policies) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + for (int i=0; i < certs.length; i++) + { + v.add(certs[i]); + } + this.certs = new DERSequence(v); + + if (policies != null) + { + v = new ASN1EncodableVector(); + for (int i=0; i < policies.length; i++) + { + v.add(policies[i]); + } + this.policies = new DERSequence(v); + } + } + + public ESSCertIDv2[] getCerts() + { + ESSCertIDv2[] certIds = new ESSCertIDv2[certs.size()]; + for (int i = 0; i != certs.size(); i++) + { + certIds[i] = ESSCertIDv2.getInstance(certs.getObjectAt(i)); + } + return certIds; + } + + public PolicyInformation[] getPolicies() + { + if (policies == null) + { + return null; + } + + PolicyInformation[] policyInformations = new PolicyInformation[policies.size()]; + for (int i = 0; i != policies.size(); i++) + { + policyInformations[i] = PolicyInformation.getInstance(policies.getObjectAt(i)); + } + return policyInformations; + } + + /** + * The definition of SigningCertificateV2 is + *
+     * SigningCertificateV2 ::=  SEQUENCE {
+     *      certs        SEQUENCE OF ESSCertIDv2,
+     *      policies     SEQUENCE OF PolicyInformation OPTIONAL
+     * }
+     * 
+ * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::= { iso(1) + * member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) + * smime(16) id-aa(2) 47 } + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(certs); + + if (policies != null) + { + v.add(policies); + } + + return new DERSequence(v); + } +} \ No newline at end of file diff --git a/src/com/google/bitcoin/bouncycastle/asn1/gnu/GNUObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/gnu/GNUObjectIdentifiers.java new file mode 100644 index 000000000..6b252c093 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/gnu/GNUObjectIdentifiers.java @@ -0,0 +1,30 @@ +package com.google.bitcoin.bouncycastle.asn1.gnu; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface GNUObjectIdentifiers +{ + public static final DERObjectIdentifier GNU = new DERObjectIdentifier("1.3.6.1.4.1.11591.1"); // GNU Radius + public static final DERObjectIdentifier GnuPG = new DERObjectIdentifier("1.3.6.1.4.1.11591.2"); // GnuPG (Ägypten) + public static final DERObjectIdentifier notation = new DERObjectIdentifier("1.3.6.1.4.1.11591.2.1"); // notation + public static final DERObjectIdentifier pkaAddress = new DERObjectIdentifier("1.3.6.1.4.1.11591.2.1.1"); // pkaAddress + public static final DERObjectIdentifier GnuRadar = new DERObjectIdentifier("1.3.6.1.4.1.11591.3"); // GNU Radar + public static final DERObjectIdentifier digestAlgorithm = new DERObjectIdentifier("1.3.6.1.4.1.11591.12"); // digestAlgorithm + public static final DERObjectIdentifier Tiger_192 = new DERObjectIdentifier("1.3.6.1.4.1.11591.12.2"); // TIGER/192 + public static final DERObjectIdentifier encryptionAlgorithm = new DERObjectIdentifier("1.3.6.1.4.1.11591.13"); // encryptionAlgorithm + public static final DERObjectIdentifier Serpent = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2"); // Serpent + public static final DERObjectIdentifier Serpent_128_ECB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.1"); // Serpent-128-ECB + public static final DERObjectIdentifier Serpent_128_CBC = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.2"); // Serpent-128-CBC + public static final DERObjectIdentifier Serpent_128_OFB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.3"); // Serpent-128-OFB + public static final DERObjectIdentifier Serpent_128_CFB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.4"); // Serpent-128-CFB + public static final DERObjectIdentifier Serpent_192_ECB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.21"); // Serpent-192-ECB + public static final DERObjectIdentifier Serpent_192_CBC = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.22"); // Serpent-192-CBC + public static final DERObjectIdentifier Serpent_192_OFB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.23"); // Serpent-192-OFB + public static final DERObjectIdentifier Serpent_192_CFB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.24"); // Serpent-192-CFB + public static final DERObjectIdentifier Serpent_256_ECB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.41"); // Serpent-256-ECB + public static final DERObjectIdentifier Serpent_256_CBC = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.42"); // Serpent-256-CBC + public static final DERObjectIdentifier Serpent_256_OFB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.43"); // Serpent-256-OFB + public static final DERObjectIdentifier Serpent_256_CFB = new DERObjectIdentifier("1.3.6.1.4.1.11591.13.2.44"); // Serpent-256-CFB + public static final DERObjectIdentifier CRC = new DERObjectIdentifier("1.3.6.1.4.1.11591.14"); // CRC algorithms + public static final DERObjectIdentifier CRC32 = new DERObjectIdentifier("1.3.6.1.4.1.11591.14.1"); // CRC 32 +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/iana/IANAObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/iana/IANAObjectIdentifiers.java new file mode 100644 index 000000000..65f8deb9f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/iana/IANAObjectIdentifiers.java @@ -0,0 +1,20 @@ +package com.google.bitcoin.bouncycastle.asn1.iana; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface IANAObjectIdentifiers +{ + // id-SHA1 OBJECT IDENTIFIER ::= + // {iso(1) identified-organization(3) dod(6) internet(1) security(5) mechanisms(5) ipsec(8) isakmpOakley(1)} + // + + static final DERObjectIdentifier isakmpOakley = new DERObjectIdentifier("1.3.6.1.5.5.8.1"); + + static final DERObjectIdentifier hmacMD5 = new DERObjectIdentifier(isakmpOakley + ".1"); + static final DERObjectIdentifier hmacSHA1 = new DERObjectIdentifier(isakmpOakley + ".2"); + + static final DERObjectIdentifier hmacTIGER = new DERObjectIdentifier(isakmpOakley + ".3"); + + static final DERObjectIdentifier hmacRIPEMD160 = new DERObjectIdentifier(isakmpOakley + ".4"); + +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/icao/DataGroupHash.java b/src/com/google/bitcoin/bouncycastle/asn1/icao/DataGroupHash.java new file mode 100644 index 000000000..8e809483d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/icao/DataGroupHash.java @@ -0,0 +1,100 @@ +package com.google.bitcoin.bouncycastle.asn1.icao; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * The DataGroupHash object. + *
+ * DataGroupHash  ::=  SEQUENCE {
+ *      dataGroupNumber         DataGroupNumber,
+ *      dataGroupHashValue     OCTET STRING }
+ * 
+ * DataGroupNumber ::= INTEGER {
+ *         dataGroup1    (1),
+ *         dataGroup1    (2),
+ *         dataGroup1    (3),
+ *         dataGroup1    (4),
+ *         dataGroup1    (5),
+ *         dataGroup1    (6),
+ *         dataGroup1    (7),
+ *         dataGroup1    (8),
+ *         dataGroup1    (9),
+ *         dataGroup1    (10),
+ *         dataGroup1    (11),
+ *         dataGroup1    (12),
+ *         dataGroup1    (13),
+ *         dataGroup1    (14),
+ *         dataGroup1    (15),
+ *         dataGroup1    (16) }
+ * 
+ * 
+ */ +public class DataGroupHash + extends ASN1Encodable +{ + DERInteger dataGroupNumber; + ASN1OctetString dataGroupHashValue; + + public static DataGroupHash getInstance( + Object obj) + { + if (obj == null || obj instanceof DataGroupHash) + { + return (DataGroupHash)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new DataGroupHash(ASN1Sequence.getInstance(obj)); + } + else + { + throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName()); + } + } + + public DataGroupHash(ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + // dataGroupNumber + dataGroupNumber = DERInteger.getInstance(e.nextElement()); + // dataGroupHashValue + dataGroupHashValue = ASN1OctetString.getInstance(e.nextElement()); + } + + public DataGroupHash( + int dataGroupNumber, + ASN1OctetString dataGroupHashValue) + { + this.dataGroupNumber = new DERInteger(dataGroupNumber); + this.dataGroupHashValue = dataGroupHashValue; + } + + public int getDataGroupNumber() + { + return dataGroupNumber.getValue().intValue(); + } + + public ASN1OctetString getDataGroupHashValue() + { + return dataGroupHashValue; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector seq = new ASN1EncodableVector(); + seq.add(dataGroupNumber); + seq.add(dataGroupHashValue); + + return new DERSequence(seq); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/icao/ICAOObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/icao/ICAOObjectIdentifiers.java new file mode 100644 index 000000000..ba951ef2d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/icao/ICAOObjectIdentifiers.java @@ -0,0 +1,15 @@ +package com.google.bitcoin.bouncycastle.asn1.icao; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface ICAOObjectIdentifiers +{ + // + // base id + // + static final String id_icao = "2.23.136"; + + static final DERObjectIdentifier id_icao_mrtd = new DERObjectIdentifier(id_icao+".1"); + static final DERObjectIdentifier id_icao_mrtd_security = new DERObjectIdentifier(id_icao_mrtd+".1"); + static final DERObjectIdentifier id_icao_ldsSecurityObject = new DERObjectIdentifier(id_icao_mrtd_security+".1"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/icao/LDSSecurityObject.java b/src/com/google/bitcoin/bouncycastle/asn1/icao/LDSSecurityObject.java new file mode 100644 index 000000000..5a85e3fe6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/icao/LDSSecurityObject.java @@ -0,0 +1,125 @@ +package com.google.bitcoin.bouncycastle.asn1.icao; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +/** + * The LDSSecurityObject object. + *
+ * LDSSecurityObject ::= SEQUENCE {
+ *   version                LDSSecurityObjectVersion,
+ *   hashAlgorithm          DigestAlgorithmIdentifier,
+ *   dataGroupHashValues    SEQUENCE SIZE (2..ub-DataGroups) OF DataHashGroup}
+ *   
+ * DigestAlgorithmIdentifier ::= AlgorithmIdentifier,
+ * 
+ * LDSSecurityObjectVersion :: INTEGER {V0(0)}
+ * 
+ */ + +public class LDSSecurityObject + extends ASN1Encodable + implements ICAOObjectIdentifiers +{ + + public static final int ub_DataGroups = 16; + + DERInteger version = new DERInteger(0); + AlgorithmIdentifier digestAlgorithmIdentifier; + DataGroupHash[] datagroupHash; + + public static LDSSecurityObject getInstance( + Object obj) + { + if (obj == null || obj instanceof LDSSecurityObject) + { + return (LDSSecurityObject)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new LDSSecurityObject(ASN1Sequence.getInstance(obj)); + } + + throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName()); + } + + public LDSSecurityObject( + ASN1Sequence seq) + { + if (seq == null || seq.size() == 0) + { + throw new IllegalArgumentException("null or empty sequence passed."); + } + + Enumeration e = seq.getObjects(); + + // version + version = DERInteger.getInstance(e.nextElement()); + // digestAlgorithmIdentifier + digestAlgorithmIdentifier = AlgorithmIdentifier.getInstance(e.nextElement()); + + ASN1Sequence datagroupHashSeq = ASN1Sequence.getInstance(e.nextElement()); + + checkDatagroupHashSeqSize(datagroupHashSeq.size()); + + datagroupHash = new DataGroupHash[datagroupHashSeq.size()]; + for (int i= 0; i< datagroupHashSeq.size();i++) + { + datagroupHash[i] = DataGroupHash.getInstance(datagroupHashSeq.getObjectAt(i)); + } + + } + + public LDSSecurityObject( + AlgorithmIdentifier digestAlgorithmIdentifier, + DataGroupHash[] datagroupHash) + { + this.digestAlgorithmIdentifier = digestAlgorithmIdentifier; + this.datagroupHash = datagroupHash; + + checkDatagroupHashSeqSize(datagroupHash.length); + } + + private void checkDatagroupHashSeqSize(int size) + { + if ((size < 2) || (size > ub_DataGroups)) + { + throw new IllegalArgumentException("wrong size in DataGroupHashValues : not in (2.."+ ub_DataGroups +")"); + } + } + + public AlgorithmIdentifier getDigestAlgorithmIdentifier() + { + return digestAlgorithmIdentifier; + } + + public DataGroupHash[] getDatagroupHash() + { + return datagroupHash; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector seq = new ASN1EncodableVector(); + + seq.add(version); + seq.add(digestAlgorithmIdentifier); + + ASN1EncodableVector seqname = new ASN1EncodableVector(); + for (int i = 0; i < datagroupHash.length; i++) + { + seqname.add(datagroupHash[i]); + } + seq.add(new DERSequence(seqname)); + + return new DERSequence(seq); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/isismtt/ISISMTTObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/ISISMTTObjectIdentifiers.java new file mode 100644 index 000000000..4d880f32d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/ISISMTTObjectIdentifiers.java @@ -0,0 +1,180 @@ +package com.google.bitcoin.bouncycastle.asn1.isismtt; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface ISISMTTObjectIdentifiers +{ + + public static final DERObjectIdentifier id_isismtt = new DERObjectIdentifier("1.3.36.8"); + + public static final DERObjectIdentifier id_isismtt_cp = new DERObjectIdentifier(id_isismtt + ".1"); + + /** + * The id-isismtt-cp-accredited OID indicates that the certificate is a + * qualified certificate according to Directive 1999/93/EC of the European + * Parliament and of the Council of 13 December 1999 on a Community + * Framework for Electronic Signatures, which additionally conforms the + * special requirements of the SigG and has been issued by an accredited CA. + */ + public static final DERObjectIdentifier id_isismtt_cp_accredited = new DERObjectIdentifier(id_isismtt_cp + ".1"); + + public static final DERObjectIdentifier id_isismtt_at = new DERObjectIdentifier(id_isismtt + ".3"); + + /** + * Certificate extensionDate of certificate generation + * + *
+     *                DateOfCertGenSyntax ::= GeneralizedTime
+     * 
+ */ + public static final DERObjectIdentifier id_isismtt_at_dateOfCertGen = new DERObjectIdentifier(id_isismtt_at + ".1"); + + /** + * Attribute to indicate that the certificate holder may sign in the name of + * a third person. May also be used as extension in a certificate. + */ + public static final DERObjectIdentifier id_isismtt_at_procuration = new DERObjectIdentifier(id_isismtt_at + ".2"); + + /** + * Attribute to indicate admissions to certain professions. May be used as + * attribute in attribute certificate or as extension in a certificate + */ + public static final DERObjectIdentifier id_isismtt_at_admission = new DERObjectIdentifier(id_isismtt_at + ".3"); + + /** + * Monetary limit for transactions. The QcEuMonetaryLimit QC statement MUST + * be used in new certificates in place of the extension/attribute + * MonetaryLimit since January 1, 2004. For the sake of backward + * compatibility with certificates already in use, SigG conforming + * components MUST support MonetaryLimit (as well as QcEuLimitValue). + */ + public static final DERObjectIdentifier id_isismtt_at_monetaryLimit = new DERObjectIdentifier(id_isismtt_at + ".4"); + + /** + * A declaration of majority. May be used as attribute in attribute + * certificate or as extension in a certificate + */ + public static final DERObjectIdentifier id_isismtt_at_declarationOfMajority = new DERObjectIdentifier(id_isismtt_at + ".5"); + + /** + * + * Serial number of the smart card containing the corresponding private key + * + *
+     *                 ICCSNSyntax ::= OCTET STRING (SIZE(8..20))
+     * 
+ */ + public static final DERObjectIdentifier id_isismtt_at_iCCSN = new DERObjectIdentifier(id_isismtt_at + ".6"); + + /** + * + * Reference for a file of a smartcard that stores the public key of this + * certificate and that is used as �security anchor�. + * + *
+     *      PKReferenceSyntax ::= OCTET STRING (SIZE(20))
+     * 
+ */ + public static final DERObjectIdentifier id_isismtt_at_PKReference = new DERObjectIdentifier(id_isismtt_at + ".7"); + + /** + * Some other restriction regarding the usage of this certificate. May be + * used as attribute in attribute certificate or as extension in a + * certificate. + * + *
+     *             RestrictionSyntax ::= DirectoryString (SIZE(1..1024))
+     * 
+ * + * @see com.google.bitcoin.bouncycastle.asn1.isismtt.x509.Restriction + */ + public static final DERObjectIdentifier id_isismtt_at_restriction = new DERObjectIdentifier(id_isismtt_at + ".8"); + + /** + * + * (Single)Request extension: Clients may include this extension in a + * (single) Request to request the responder to send the certificate in the + * response message along with the status information. Besides the LDAP + * service, this extension provides another mechanism for the distribution + * of certificates, which MAY optionally be provided by certificate + * repositories. + * + *
+     *        RetrieveIfAllowed ::= BOOLEAN
+     *       
+     * 
+ */ + public static final DERObjectIdentifier id_isismtt_at_retrieveIfAllowed = new DERObjectIdentifier(id_isismtt_at + ".9"); + + /** + * SingleOCSPResponse extension: The certificate requested by the client by + * inserting the RetrieveIfAllowed extension in the request, will be + * returned in this extension. + * + * @see com.google.bitcoin.bouncycastle.asn1.isismtt.ocsp.RequestedCertificate + */ + public static final DERObjectIdentifier id_isismtt_at_requestedCertificate = new DERObjectIdentifier(id_isismtt_at + ".10"); + + /** + * Base ObjectIdentifier for naming authorities + */ + public static final DERObjectIdentifier id_isismtt_at_namingAuthorities = new DERObjectIdentifier(id_isismtt_at + ".11"); + + /** + * SingleOCSPResponse extension: Date, when certificate has been published + * in the directory and status information has become available. Currently, + * accrediting authorities enforce that SigG-conforming OCSP servers include + * this extension in the responses. + * + *
+     *      CertInDirSince ::= GeneralizedTime
+     * 
+ */ + public static final DERObjectIdentifier id_isismtt_at_certInDirSince = new DERObjectIdentifier(id_isismtt_at + ".12"); + + /** + * Hash of a certificate in OCSP. + * + * @see com.google.bitcoin.bouncycastle.asn1.isismtt.ocsp.CertHash + */ + public static final DERObjectIdentifier id_isismtt_at_certHash = new DERObjectIdentifier(id_isismtt_at + ".13"); + + /** + *
+     *          NameAtBirth ::= DirectoryString(SIZE(1..64)
+     * 
+ * + * Used in + * {@link com.google.bitcoin.bouncycastle.asn1.x509.SubjectDirectoryAttributes SubjectDirectoryAttributes} + */ + public static final DERObjectIdentifier id_isismtt_at_nameAtBirth = new DERObjectIdentifier(id_isismtt_at + ".14"); + + /** + * Some other information of non-restrictive nature regarding the usage of + * this certificate. May be used as attribute in atribute certificate or as + * extension in a certificate. + * + *
+     *               AdditionalInformationSyntax ::= DirectoryString (SIZE(1..2048))
+     * 
+ * + * @see com.google.bitcoin.bouncycastle.asn1.isismtt.x509.AdditionalInformationSyntax + */ + public static final DERObjectIdentifier id_isismtt_at_additionalInformation = new DERObjectIdentifier(id_isismtt_at + ".15"); + + /** + * Indicates that an attribute certificate exists, which limits the + * usability of this public key certificate. Whenever verifying a signature + * with the help of this certificate, the content of the corresponding + * attribute certificate should be concerned. This extension MUST be + * included in a PKC, if a corresponding attribute certificate (having the + * PKC as base certificate) contains some attribute that restricts the + * usability of the PKC too. Attribute certificates with restricting content + * MUST always be included in the signed document. + * + *
+     *                   LiabilityLimitationFlagSyntax ::= BOOLEAN
+     * 
+ */ + public static final DERObjectIdentifier id_isismtt_at_liabilityLimitationFlag = new DERObjectIdentifier("0.2.262.1.10.12.0"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/isismtt/ocsp/CertHash.java b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/ocsp/CertHash.java new file mode 100644 index 000000000..428303785 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/ocsp/CertHash.java @@ -0,0 +1,124 @@ +package com.google.bitcoin.bouncycastle.asn1.isismtt.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +/** + * ISIS-MTT PROFILE: The responder may include this extension in a response to + * send the hash of the requested certificate to the responder. This hash is + * cryptographically bound to the certificate and serves as evidence that the + * certificate is known to the responder (i.e. it has been issued and is present + * in the directory). Hence, this extension is a means to provide a positive + * statement of availability as described in T8.[8]. As explained in T13.[1], + * clients may rely on this information to be able to validate signatures after + * the expiry of the corresponding certificate. Hence, clients MUST support this + * extension. If a positive statement of availability is to be delivered, this + * extension syntax and OID MUST be used. + *

+ *

+ *

+ *     CertHash ::= SEQUENCE {
+ *       hashAlgorithm AlgorithmIdentifier,
+ *       certificateHash OCTET STRING
+ *     }
+ * 
+ */ +public class CertHash + extends ASN1Encodable +{ + + private AlgorithmIdentifier hashAlgorithm; + private byte[] certificateHash; + + public static CertHash getInstance(Object obj) + { + if (obj == null || obj instanceof CertHash) + { + return (CertHash)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new CertHash((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + + obj.getClass().getName()); + } + + /** + * Constructor from ASN1Sequence. + *

+ * The sequence is of type CertHash: + *

+ *

+     *     CertHash ::= SEQUENCE {
+     *       hashAlgorithm AlgorithmIdentifier,
+     *       certificateHash OCTET STRING
+     *     }
+     * 
+ * + * @param seq The ASN.1 sequence. + */ + private CertHash(ASN1Sequence seq) + { + if (seq.size() != 2) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + hashAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(0)); + certificateHash = DEROctetString.getInstance(seq.getObjectAt(1)).getOctets(); + } + + /** + * Constructor from a given details. + * + * @param hashAlgorithm The hash algorithm identifier. + * @param certificateHash The hash of the whole DER encoding of the certificate. + */ + public CertHash(AlgorithmIdentifier hashAlgorithm, byte[] certificateHash) + { + this.hashAlgorithm = hashAlgorithm; + this.certificateHash = new byte[certificateHash.length]; + System.arraycopy(certificateHash, 0, this.certificateHash, 0, + certificateHash.length); + } + + public AlgorithmIdentifier getHashAlgorithm() + { + return hashAlgorithm; + } + + public byte[] getCertificateHash() + { + return certificateHash; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+ * Returns: + *

+ *

+     *     CertHash ::= SEQUENCE {
+     *       hashAlgorithm AlgorithmIdentifier,
+     *       certificateHash OCTET STRING
+     *     }
+     * 
+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + ASN1EncodableVector vec = new ASN1EncodableVector(); + vec.add(hashAlgorithm); + vec.add(new DEROctetString(certificateHash)); + return new DERSequence(vec); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/isismtt/ocsp/RequestedCertificate.java b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/ocsp/RequestedCertificate.java new file mode 100644 index 000000000..08ca5e50a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/ocsp/RequestedCertificate.java @@ -0,0 +1,183 @@ +package com.google.bitcoin.bouncycastle.asn1.isismtt.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.X509CertificateStructure; + +import java.io.IOException; + +/** + * ISIS-MTT-Optional: The certificate requested by the client by inserting the + * RetrieveIfAllowed extension in the request, will be returned in this + * extension. + *

+ * ISIS-MTT-SigG: The signature act allows publishing certificates only then, + * when the certificate owner gives his explicit permission. Accordingly, there + * may be �nondownloadable� certificates, about which the responder must provide + * status information, but MUST NOT include them in the response. Clients may + * get therefore the following three kind of answers on a single request + * including the RetrieveIfAllowed extension: + *

    + *
  • a) the responder supports the extension and is allowed to publish the + * certificate: RequestedCertificate returned including the requested + * certificate + *
  • b) the responder supports the extension but is NOT allowed to publish + * the certificate: RequestedCertificate returned including an empty OCTET + * STRING + *
  • c) the responder does not support the extension: RequestedCertificate is + * not included in the response + *
+ * Clients requesting RetrieveIfAllowed MUST be able to handle these cases. If + * any of the OCTET STRING options is used, it MUST contain the DER encoding of + * the requested certificate. + *

+ *

+ *            RequestedCertificate ::= CHOICE {
+ *              Certificate Certificate,
+ *              publicKeyCertificate [0] EXPLICIT OCTET STRING,
+ *              attributeCertificate [1] EXPLICIT OCTET STRING
+ *            }
+ * 
+ */ +public class RequestedCertificate + extends ASN1Encodable + implements ASN1Choice +{ + public static final int certificate = -1; + public static final int publicKeyCertificate = 0; + public static final int attributeCertificate = 1; + + private X509CertificateStructure cert; + private byte[] publicKeyCert; + private byte[] attributeCert; + + public static RequestedCertificate getInstance(Object obj) + { + if (obj == null || obj instanceof RequestedCertificate) + { + return (RequestedCertificate)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new RequestedCertificate(X509CertificateStructure.getInstance(obj)); + } + if (obj instanceof ASN1TaggedObject) + { + return new RequestedCertificate((ASN1TaggedObject)obj); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + + obj.getClass().getName()); + } + + public static RequestedCertificate getInstance(ASN1TaggedObject obj, boolean explicit) + { + if (!explicit) + { + throw new IllegalArgumentException("choice item must be explicitly tagged"); + } + + return getInstance(obj.getObject()); + } + + private RequestedCertificate(ASN1TaggedObject tagged) + { + if (tagged.getTagNo() == publicKeyCertificate) + { + publicKeyCert = ASN1OctetString.getInstance(tagged, true).getOctets(); + } + else if (tagged.getTagNo() == attributeCertificate) + { + attributeCert = ASN1OctetString.getInstance(tagged, true).getOctets(); + } + else + { + throw new IllegalArgumentException("unknown tag number: " + tagged.getTagNo()); + } + } + + /** + * Constructor from a given details. + *

+ * Only one parameter can be given. All other must be null. + * + * @param certificate Given as Certificate + */ + public RequestedCertificate(X509CertificateStructure certificate) + { + this.cert = certificate; + } + + public RequestedCertificate(int type, byte[] certificateOctets) + { + this(new DERTaggedObject(type, new DEROctetString(certificateOctets))); + } + + public int getType() + { + if (cert != null) + { + return certificate; + } + if (publicKeyCert != null) + { + return publicKeyCertificate; + } + return attributeCertificate; + } + + public byte[] getCertificateBytes() + { + if (cert != null) + { + try + { + return cert.getEncoded(); + } + catch (IOException e) + { + throw new IllegalStateException("can't decode certificate: " + e); + } + } + if (publicKeyCert != null) + { + return publicKeyCert; + } + return attributeCert; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+ * Returns: + *

+ *

+     *            RequestedCertificate ::= CHOICE {
+     *              Certificate Certificate,
+     *              publicKeyCertificate [0] EXPLICIT OCTET STRING,
+     *              attributeCertificate [1] EXPLICIT OCTET STRING
+     *            }
+     * 
+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + if (publicKeyCert != null) + { + return new DERTaggedObject(0, new DEROctetString(publicKeyCert)); + } + if (attributeCert != null) + { + return new DERTaggedObject(1, new DEROctetString(attributeCert)); + } + return cert.getDERObject(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/AdditionalInformationSyntax.java b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/AdditionalInformationSyntax.java new file mode 100644 index 000000000..0ad648a68 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/AdditionalInformationSyntax.java @@ -0,0 +1,70 @@ +package com.google.bitcoin.bouncycastle.asn1.isismtt.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERString; +import com.google.bitcoin.bouncycastle.asn1.x500.DirectoryString; + +/** + * Some other information of non-restrictive nature regarding the usage of this + * certificate. + * + *
+ *    AdditionalInformationSyntax ::= DirectoryString (SIZE(1..2048))
+ * 
+ */ +public class AdditionalInformationSyntax extends ASN1Encodable +{ + private DirectoryString information; + + public static AdditionalInformationSyntax getInstance(Object obj) + { + if (obj == null || obj instanceof AdditionalInformationSyntax) + { + return (AdditionalInformationSyntax)obj; + } + + if (obj instanceof DERString) + { + return new AdditionalInformationSyntax(DirectoryString.getInstance(obj)); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + private AdditionalInformationSyntax(DirectoryString information) + { + this.information = information; + } + + /** + * Constructor from a given details. + * + * @param information The describtion of the information. + */ + public AdditionalInformationSyntax(String information) + { + this(new DirectoryString(information)); + } + + public DirectoryString getInformation() + { + return information; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+ * Returns: + *

+ *

+     *   AdditionalInformationSyntax ::= DirectoryString (SIZE(1..2048))
+     * 
+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + return information.toASN1Object(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/AdmissionSyntax.java b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/AdmissionSyntax.java new file mode 100644 index 000000000..a23c9a088 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/AdmissionSyntax.java @@ -0,0 +1,280 @@ +package com.google.bitcoin.bouncycastle.asn1.isismtt.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.GeneralName; + +import java.util.Enumeration; + +/** + * Attribute to indicate admissions to certain professions. + *

+ *

+ *     AdmissionSyntax ::= SEQUENCE
+ *     {
+ *       admissionAuthority GeneralName OPTIONAL,
+ *       contentsOfAdmissions SEQUENCE OF Admissions
+ *     }
+ * 

+ * Admissions ::= SEQUENCE + * { + * admissionAuthority [0] EXPLICIT GeneralName OPTIONAL + * namingAuthority [1] EXPLICIT NamingAuthority OPTIONAL + * professionInfos SEQUENCE OF ProfessionInfo + * } + *

+ * NamingAuthority ::= SEQUENCE + * { + * namingAuthorityId OBJECT IDENTIFIER OPTIONAL, + * namingAuthorityUrl IA5String OPTIONAL, + * namingAuthorityText DirectoryString(SIZE(1..128)) OPTIONAL + * } + *

+ * ProfessionInfo ::= SEQUENCE + * { + * namingAuthority [0] EXPLICIT NamingAuthority OPTIONAL, + * professionItems SEQUENCE OF DirectoryString (SIZE(1..128)), + * professionOIDs SEQUENCE OF OBJECT IDENTIFIER OPTIONAL, + * registrationNumber PrintableString(SIZE(1..128)) OPTIONAL, + * addProfessionInfo OCTET STRING OPTIONAL + * } + *

+ *

+ *

+ * ISIS-MTT PROFILE: The relatively complex structure of AdmissionSyntax + * supports the following concepts and requirements: + *

    + *
  • External institutions (e.g. professional associations, chambers, unions, + * administrative bodies, companies, etc.), which are responsible for granting + * and verifying professional admissions, are indicated by means of the data + * field admissionAuthority. An admission authority is indicated by a + * GeneralName object. Here an X.501 directory name (distinguished name) can be + * indicated in the field directoryName, a URL address can be indicated in the + * field uniformResourceIdentifier, and an object identifier can be indicated in + * the field registeredId. + *
  • The names of authorities which are responsible for the administration of + * title registers are indicated in the data field namingAuthority. The name of + * the authority can be identified by an object identifier in the field + * namingAuthorityId, by means of a text string in the field + * namingAuthorityText, by means of a URL address in the field + * namingAuthorityUrl, or by a combination of them. For example, the text string + * can contain the name of the authority, the country and the name of the title + * register. The URL-option refers to a web page which contains lists with + * �officially� registered professions (text and possibly OID) as well as + * further information on these professions. Object identifiers for the + * component namingAuthorityId are grouped under the OID-branch + * id-isis-at-namingAuthorities and must be applied for. + *
  • See + * http://www.teletrust.de/anwend.asp?Id=30200&Sprache=E_&HomePG=0 for + * an application form and http://www.teletrust.de/links.asp?id=30220,11 + * for an overview of registered naming authorities. + *
  • By means of the data type ProfessionInfo certain professions, + * specializations, disciplines, fields of activity, etc. are identified. A + * profession is represented by one or more text strings, resp. profession OIDs + * in the fields professionItems and professionOIDs and by a registration number + * in the field registrationNumber. An indication in text form must always be + * present, whereas the other indications are optional. The component + * addProfessionInfo may contain additional applicationspecific information in + * DER-encoded form. + *
+ *

+ * By means of different namingAuthority-OIDs or profession OIDs hierarchies of + * professions, specializations, disciplines, fields of activity, etc. can be + * expressed. The issuing admission authority should always be indicated (field + * admissionAuthority), whenever a registration number is presented. Still, + * information on admissions can be given without indicating an admission or a + * naming authority by the exclusive use of the component professionItems. In + * this case the certification authority is responsible for the verification of + * the admission information. + *

+ *

+ *

+ * This attribute is single-valued. Still, several admissions can be captured in + * the sequence structure of the component contentsOfAdmissions of + * AdmissionSyntax or in the component professionInfos of Admissions. The + * component admissionAuthority of AdmissionSyntax serves as default value for + * the component admissionAuthority of Admissions. Within the latter component + * the default value can be overwritten, in case that another authority is + * responsible. The component namingAuthority of Admissions serves as a default + * value for the component namingAuthority of ProfessionInfo. Within the latter + * component the default value can be overwritten, in case that another naming + * authority needs to be recorded. + *

+ * The length of the string objects is limited to 128 characters. It is + * recommended to indicate a namingAuthorityURL in all issued attribute + * certificates. If a namingAuthorityURL is indicated, the field professionItems + * of ProfessionInfo should contain only registered titles. If the field + * professionOIDs exists, it has to contain the OIDs of the professions listed + * in professionItems in the same order. In general, the field professionInfos + * should contain only one entry, unless the admissions that are to be listed + * are logically connected (e.g. they have been issued under the same admission + * number). + * + * @see com.google.bitcoin.bouncycastle.asn1.isismtt.x509.Admissions + * @see com.google.bitcoin.bouncycastle.asn1.isismtt.x509.ProfessionInfo + * @see com.google.bitcoin.bouncycastle.asn1.isismtt.x509.NamingAuthority + */ +public class AdmissionSyntax + extends ASN1Encodable +{ + + private GeneralName admissionAuthority; + + private ASN1Sequence contentsOfAdmissions; + + public static AdmissionSyntax getInstance(Object obj) + { + if (obj == null || obj instanceof AdmissionSyntax) + { + return (AdmissionSyntax)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new AdmissionSyntax((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + + obj.getClass().getName()); + } + + /** + * Constructor from ASN1Sequence. + *

+ * The sequence is of type ProcurationSyntax: + *

+ *

+     *     AdmissionSyntax ::= SEQUENCE
+     *     {
+     *       admissionAuthority GeneralName OPTIONAL,
+     *       contentsOfAdmissions SEQUENCE OF Admissions
+     *     }
+     * 

+ * Admissions ::= SEQUENCE + * { + * admissionAuthority [0] EXPLICIT GeneralName OPTIONAL + * namingAuthority [1] EXPLICIT NamingAuthority OPTIONAL + * professionInfos SEQUENCE OF ProfessionInfo + * } + *

+ * NamingAuthority ::= SEQUENCE + * { + * namingAuthorityId OBJECT IDENTIFIER OPTIONAL, + * namingAuthorityUrl IA5String OPTIONAL, + * namingAuthorityText DirectoryString(SIZE(1..128)) OPTIONAL + * } + *

+ * ProfessionInfo ::= SEQUENCE + * { + * namingAuthority [0] EXPLICIT NamingAuthority OPTIONAL, + * professionItems SEQUENCE OF DirectoryString (SIZE(1..128)), + * professionOIDs SEQUENCE OF OBJECT IDENTIFIER OPTIONAL, + * registrationNumber PrintableString(SIZE(1..128)) OPTIONAL, + * addProfessionInfo OCTET STRING OPTIONAL + * } + *

+ * + * @param seq The ASN.1 sequence. + */ + private AdmissionSyntax(ASN1Sequence seq) + { + switch (seq.size()) + { + case 1: + contentsOfAdmissions = DERSequence.getInstance(seq.getObjectAt(0)); + break; + case 2: + admissionAuthority = GeneralName.getInstance(seq.getObjectAt(0)); + contentsOfAdmissions = DERSequence.getInstance(seq.getObjectAt(1)); + break; + default: + throw new IllegalArgumentException("Bad sequence size: " + seq.size()); + } + } + + /** + * Constructor from given details. + * + * @param admissionAuthority The admission authority. + * @param contentsOfAdmissions The admissions. + */ + public AdmissionSyntax(GeneralName admissionAuthority, ASN1Sequence contentsOfAdmissions) + { + this.admissionAuthority = admissionAuthority; + this.contentsOfAdmissions = contentsOfAdmissions; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+ * Returns: + *

+ *

+     *     AdmissionSyntax ::= SEQUENCE
+     *     {
+     *       admissionAuthority GeneralName OPTIONAL,
+     *       contentsOfAdmissions SEQUENCE OF Admissions
+     *     }
+     * 

+ * Admissions ::= SEQUENCE + * { + * admissionAuthority [0] EXPLICIT GeneralName OPTIONAL + * namingAuthority [1] EXPLICIT NamingAuthority OPTIONAL + * professionInfos SEQUENCE OF ProfessionInfo + * } + *

+ * NamingAuthority ::= SEQUENCE + * { + * namingAuthorityId OBJECT IDENTIFIER OPTIONAL, + * namingAuthorityUrl IA5String OPTIONAL, + * namingAuthorityText DirectoryString(SIZE(1..128)) OPTIONAL + * } + *

+ * ProfessionInfo ::= SEQUENCE + * { + * namingAuthority [0] EXPLICIT NamingAuthority OPTIONAL, + * professionItems SEQUENCE OF DirectoryString (SIZE(1..128)), + * professionOIDs SEQUENCE OF OBJECT IDENTIFIER OPTIONAL, + * registrationNumber PrintableString(SIZE(1..128)) OPTIONAL, + * addProfessionInfo OCTET STRING OPTIONAL + * } + *

+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + ASN1EncodableVector vec = new ASN1EncodableVector(); + if (admissionAuthority != null) + { + vec.add(admissionAuthority); + } + vec.add(contentsOfAdmissions); + return new DERSequence(vec); + } + + /** + * @return Returns the admissionAuthority if present, null otherwise. + */ + public GeneralName getAdmissionAuthority() + { + return admissionAuthority; + } + + /** + * @return Returns the contentsOfAdmissions. + */ + public Admissions[] getContentsOfAdmissions() + { + Admissions[] admissions = new Admissions[contentsOfAdmissions.size()]; + int count = 0; + for (Enumeration e = contentsOfAdmissions.getObjects(); e.hasMoreElements();) + { + admissions[count++] = Admissions.getInstance(e.nextElement()); + } + return admissions; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/Admissions.java b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/Admissions.java new file mode 100644 index 000000000..f027ebd47 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/Admissions.java @@ -0,0 +1,188 @@ +package com.google.bitcoin.bouncycastle.asn1.isismtt.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.GeneralName; + +import java.util.Enumeration; + +/** + * An Admissions structure. + *

+ *

+ *            Admissions ::= SEQUENCE
+ *            {
+ *              admissionAuthority [0] EXPLICIT GeneralName OPTIONAL
+ *              namingAuthority [1] EXPLICIT NamingAuthority OPTIONAL
+ *              professionInfos SEQUENCE OF ProfessionInfo
+ *            }
+ * 

+ *

+ * + * @see com.google.bitcoin.bouncycastle.asn1.isismtt.x509.AdmissionSyntax + * @see com.google.bitcoin.bouncycastle.asn1.isismtt.x509.ProfessionInfo + * @see com.google.bitcoin.bouncycastle.asn1.isismtt.x509.NamingAuthority + */ +public class Admissions extends ASN1Encodable +{ + + private GeneralName admissionAuthority; + + private NamingAuthority namingAuthority; + + private ASN1Sequence professionInfos; + + public static Admissions getInstance(Object obj) + { + if (obj == null || obj instanceof Admissions) + { + return (Admissions)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new Admissions((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * Constructor from ASN1Sequence. + *

+ * The sequence is of type ProcurationSyntax: + *

+ *

+     *            Admissions ::= SEQUENCE
+     *            {
+     *              admissionAuthority [0] EXPLICIT GeneralName OPTIONAL
+     *              namingAuthority [1] EXPLICIT NamingAuthority OPTIONAL
+     *              professionInfos SEQUENCE OF ProfessionInfo
+     *            }
+     * 
+ * + * @param seq The ASN.1 sequence. + */ + private Admissions(ASN1Sequence seq) + { + if (seq.size() > 3) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + Enumeration e = seq.getObjects(); + + DEREncodable o = (DEREncodable)e.nextElement(); + if (o instanceof ASN1TaggedObject) + { + switch (((ASN1TaggedObject)o).getTagNo()) + { + case 0: + admissionAuthority = GeneralName.getInstance((ASN1TaggedObject)o, true); + break; + case 1: + namingAuthority = NamingAuthority.getInstance((ASN1TaggedObject)o, true); + break; + default: + throw new IllegalArgumentException("Bad tag number: " + ((ASN1TaggedObject)o).getTagNo()); + } + o = (DEREncodable)e.nextElement(); + } + if (o instanceof ASN1TaggedObject) + { + switch (((ASN1TaggedObject)o).getTagNo()) + { + case 1: + namingAuthority = NamingAuthority.getInstance((ASN1TaggedObject)o, true); + break; + default: + throw new IllegalArgumentException("Bad tag number: " + ((ASN1TaggedObject)o).getTagNo()); + } + o = (DEREncodable)e.nextElement(); + } + professionInfos = ASN1Sequence.getInstance(o); + if (e.hasMoreElements()) + { + throw new IllegalArgumentException("Bad object encountered: " + + e.nextElement().getClass()); + } + } + + /** + * Constructor from a given details. + *

+ * Parameter professionInfos is mandatory. + * + * @param admissionAuthority The admission authority. + * @param namingAuthority The naming authority. + * @param professionInfos The profession infos. + */ + public Admissions(GeneralName admissionAuthority, + NamingAuthority namingAuthority, ProfessionInfo[] professionInfos) + { + this.admissionAuthority = admissionAuthority; + this.namingAuthority = namingAuthority; + this.professionInfos = new DERSequence(professionInfos); + } + + public GeneralName getAdmissionAuthority() + { + return admissionAuthority; + } + + public NamingAuthority getNamingAuthority() + { + return namingAuthority; + } + + public ProfessionInfo[] getProfessionInfos() + { + ProfessionInfo[] infos = new ProfessionInfo[professionInfos.size()]; + int count = 0; + for (Enumeration e = professionInfos.getObjects(); e.hasMoreElements();) + { + infos[count++] = ProfessionInfo.getInstance(e.nextElement()); + } + return infos; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+ * Returns: + *

+ *

+     *       Admissions ::= SEQUENCE
+     *       {
+     *         admissionAuthority [0] EXPLICIT GeneralName OPTIONAL
+     *         namingAuthority [1] EXPLICIT NamingAuthority OPTIONAL
+     *         professionInfos SEQUENCE OF ProfessionInfo
+     *       }
+     * 

+ *

+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + ASN1EncodableVector vec = new ASN1EncodableVector(); + + if (admissionAuthority != null) + { + vec.add(new DERTaggedObject(true, 0, admissionAuthority)); + } + if (namingAuthority != null) + { + vec.add(new DERTaggedObject(true, 1, namingAuthority)); + } + vec.add(professionInfos); + + return new DERSequence(vec); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/DeclarationOfMajority.java b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/DeclarationOfMajority.java new file mode 100644 index 000000000..19c49cb4e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/DeclarationOfMajority.java @@ -0,0 +1,164 @@ +package com.google.bitcoin.bouncycastle.asn1.isismtt.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBoolean; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERPrintableString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +/** + * A declaration of majority. + *

+ *

+ *           DeclarationOfMajoritySyntax ::= CHOICE
+ *           {
+ *             notYoungerThan [0] IMPLICIT INTEGER,
+ *             fullAgeAtCountry [1] IMPLICIT SEQUENCE
+ *             {
+ *               fullAge BOOLEAN DEFAULT TRUE,
+ *               country PrintableString (SIZE(2))
+ *             }
+ *             dateOfBirth [2] IMPLICIT GeneralizedTime
+ *           }
+ * 
+ *

+ * fullAgeAtCountry indicates the majority of the owner with respect to the laws + * of a specific country. + */ +public class DeclarationOfMajority + extends ASN1Encodable + implements ASN1Choice +{ + public static final int notYoungerThan = 0; + public static final int fullAgeAtCountry = 1; + public static final int dateOfBirth = 2; + + private ASN1TaggedObject declaration; + + public DeclarationOfMajority(int notYoungerThan) + { + declaration = new DERTaggedObject(false, 0, new DERInteger(notYoungerThan)); + } + + public DeclarationOfMajority(boolean fullAge, String country) + { + if (country.length() > 2) + { + throw new IllegalArgumentException("country can only be 2 characters"); + } + + if (fullAge) + { + declaration = new DERTaggedObject(false, 1, new DERSequence(new DERPrintableString(country, true))); + } + else + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(DERBoolean.FALSE); + v.add(new DERPrintableString(country, true)); + + declaration = new DERTaggedObject(false, 1, new DERSequence(v)); + } + } + + public DeclarationOfMajority(DERGeneralizedTime dateOfBirth) + { + declaration = new DERTaggedObject(false, 2, dateOfBirth); + } + + public static DeclarationOfMajority getInstance(Object obj) + { + if (obj == null || obj instanceof DeclarationOfMajority) + { + return (DeclarationOfMajority)obj; + } + + if (obj instanceof ASN1TaggedObject) + { + return new DeclarationOfMajority((ASN1TaggedObject)obj); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + + obj.getClass().getName()); + } + + private DeclarationOfMajority(ASN1TaggedObject o) + { + if (o.getTagNo() > 2) + { + throw new IllegalArgumentException("Bad tag number: " + o.getTagNo()); + } + declaration = o; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+ * Returns: + *

+ *

+     *           DeclarationOfMajoritySyntax ::= CHOICE
+     *           {
+     *             notYoungerThan [0] IMPLICIT INTEGER,
+     *             fullAgeAtCountry [1] IMPLICIT SEQUENCE
+     *             {
+     *               fullAge BOOLEAN DEFAULT TRUE,
+     *               country PrintableString (SIZE(2))
+     *             }
+     *             dateOfBirth [2] IMPLICIT GeneralizedTime
+     *           }
+     * 
+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + return declaration; + } + + public int getType() + { + return declaration.getTagNo(); + } + + /** + * @return notYoungerThan if that's what we are, -1 otherwise + */ + public int notYoungerThan() + { + if (declaration.getTagNo() != 0) + { + return -1; + } + + return DERInteger.getInstance(declaration, false).getValue().intValue(); + } + + public ASN1Sequence fullAgeAtCountry() + { + if (declaration.getTagNo() != 1) + { + return null; + } + + return ASN1Sequence.getInstance(declaration, false); + } + + public DERGeneralizedTime getDateOfBirth() + { + if (declaration.getTagNo() != 2) + { + return null; + } + + return DERGeneralizedTime.getInstance(declaration, false); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/MonetaryLimit.java b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/MonetaryLimit.java new file mode 100644 index 000000000..2a5a4fc5b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/MonetaryLimit.java @@ -0,0 +1,131 @@ +package com.google.bitcoin.bouncycastle.asn1.isismtt.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERPrintableString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +import java.math.BigInteger; +import java.util.Enumeration; + +/** + * Monetary limit for transactions. The QcEuMonetaryLimit QC statement MUST be + * used in new certificates in place of the extension/attribute MonetaryLimit + * since January 1, 2004. For the sake of backward compatibility with + * certificates already in use, components SHOULD support MonetaryLimit (as well + * as QcEuLimitValue). + *

+ * Indicates a monetary limit within which the certificate holder is authorized + * to act. (This value DOES NOT express a limit on the liability of the + * certification authority). + *

+ *

+ *    MonetaryLimitSyntax ::= SEQUENCE
+ *    {
+ *      currency PrintableString (SIZE(3)),
+ *      amount INTEGER,
+ *      exponent INTEGER
+ *    }
+ * 
+ *

+ * currency must be the ISO code. + *

+ * value = amount�10*exponent + */ +public class MonetaryLimit + extends ASN1Encodable +{ + DERPrintableString currency; + DERInteger amount; + DERInteger exponent; + + public static MonetaryLimit getInstance(Object obj) + { + if (obj == null || obj instanceof MonetaryLimit) + { + return (MonetaryLimit)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new MonetaryLimit(ASN1Sequence.getInstance(obj)); + } + + throw new IllegalArgumentException("unknown object in getInstance"); + } + + private MonetaryLimit(ASN1Sequence seq) + { + if (seq.size() != 3) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + Enumeration e = seq.getObjects(); + currency = DERPrintableString.getInstance(e.nextElement()); + amount = DERInteger.getInstance(e.nextElement()); + exponent = DERInteger.getInstance(e.nextElement()); + } + + /** + * Constructor from a given details. + *

+ *

+ * value = amount�10^exponent + * + * @param currency The currency. Must be the ISO code. + * @param amount The amount + * @param exponent The exponent + */ + public MonetaryLimit(String currency, int amount, int exponent) + { + this.currency = new DERPrintableString(currency, true); + this.amount = new DERInteger(amount); + this.exponent = new DERInteger(exponent); + } + + public String getCurrency() + { + return currency.getString(); + } + + public BigInteger getAmount() + { + return amount.getValue(); + } + + public BigInteger getExponent() + { + return exponent.getValue(); + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+ * Returns: + *

+ *

+     *    MonetaryLimitSyntax ::= SEQUENCE
+     *    {
+     *      currency PrintableString (SIZE(3)),
+     *      amount INTEGER,
+     *      exponent INTEGER
+     *    }
+     * 
+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + ASN1EncodableVector seq = new ASN1EncodableVector(); + seq.add(currency); + seq.add(amount); + seq.add(exponent); + + return new DERSequence(seq); + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/NamingAuthority.java b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/NamingAuthority.java new file mode 100644 index 000000000..af2fdb973 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/NamingAuthority.java @@ -0,0 +1,225 @@ +package com.google.bitcoin.bouncycastle.asn1.isismtt.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERIA5String; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERString; +import com.google.bitcoin.bouncycastle.asn1.isismtt.ISISMTTObjectIdentifiers; +import com.google.bitcoin.bouncycastle.asn1.x500.DirectoryString; + +import java.util.Enumeration; + +/** + * Names of authorities which are responsible for the administration of title + * registers. + * + *
+ *             NamingAuthority ::= SEQUENCE 
+ *             {
+ *               namingAuthorityId OBJECT IDENTIFIER OPTIONAL,
+ *               namingAuthorityUrl IA5String OPTIONAL,
+ *               namingAuthorityText DirectoryString(SIZE(1..128)) OPTIONAL
+ *             }
+ * 
+ * @see com.google.bitcoin.bouncycastle.asn1.isismtt.x509.AdmissionSyntax + * + */ +public class NamingAuthority + extends ASN1Encodable +{ + + /** + * Profession OIDs should always be defined under the OID branch of the + * responsible naming authority. At the time of this writing, the work group + * �Recht, Wirtschaft, Steuern� (�Law, Economy, Taxes�) is registered as the + * first naming authority under the OID id-isismtt-at-namingAuthorities. + */ + public static final DERObjectIdentifier id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern = + new DERObjectIdentifier(ISISMTTObjectIdentifiers.id_isismtt_at_namingAuthorities + ".1"); + + private DERObjectIdentifier namingAuthorityId; + private String namingAuthorityUrl; + private DirectoryString namingAuthorityText; + + public static NamingAuthority getInstance(Object obj) + { + if (obj == null || obj instanceof NamingAuthority) + { + return (NamingAuthority)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new NamingAuthority((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + + obj.getClass().getName()); + } + + public static NamingAuthority getInstance(ASN1TaggedObject obj, boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * Constructor from ASN1Sequence. + *

+ *

+ *

+     *             NamingAuthority ::= SEQUENCE
+     *             {
+     *               namingAuthorityId OBJECT IDENTIFIER OPTIONAL,
+     *               namingAuthorityUrl IA5String OPTIONAL,
+     *               namingAuthorityText DirectoryString(SIZE(1..128)) OPTIONAL
+     *             }
+     * 
+ * + * @param seq The ASN.1 sequence. + */ + private NamingAuthority(ASN1Sequence seq) + { + + if (seq.size() > 3) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + Enumeration e = seq.getObjects(); + + if (e.hasMoreElements()) + { + DEREncodable o = (DEREncodable)e.nextElement(); + if (o instanceof DERObjectIdentifier) + { + namingAuthorityId = (DERObjectIdentifier)o; + } + else if (o instanceof DERIA5String) + { + namingAuthorityUrl = DERIA5String.getInstance(o).getString(); + } + else if (o instanceof DERString) + { + namingAuthorityText = DirectoryString.getInstance(o); + } + else + { + throw new IllegalArgumentException("Bad object encountered: " + + o.getClass()); + } + } + if (e.hasMoreElements()) + { + DEREncodable o = (DEREncodable)e.nextElement(); + if (o instanceof DERIA5String) + { + namingAuthorityUrl = DERIA5String.getInstance(o).getString(); + } + else if (o instanceof DERString) + { + namingAuthorityText = DirectoryString.getInstance(o); + } + else + { + throw new IllegalArgumentException("Bad object encountered: " + + o.getClass()); + } + } + if (e.hasMoreElements()) + { + DEREncodable o = (DEREncodable)e.nextElement(); + if (o instanceof DERString) + { + namingAuthorityText = DirectoryString.getInstance(o); + } + else + { + throw new IllegalArgumentException("Bad object encountered: " + + o.getClass()); + } + + } + } + + /** + * @return Returns the namingAuthorityId. + */ + public DERObjectIdentifier getNamingAuthorityId() + { + return namingAuthorityId; + } + + /** + * @return Returns the namingAuthorityText. + */ + public DirectoryString getNamingAuthorityText() + { + return namingAuthorityText; + } + + /** + * @return Returns the namingAuthorityUrl. + */ + public String getNamingAuthorityUrl() + { + return namingAuthorityUrl; + } + + /** + * Constructor from given details. + *

+ * All parameters can be combined. + * + * @param namingAuthorityId ObjectIdentifier for naming authority. + * @param namingAuthorityUrl URL for naming authority. + * @param namingAuthorityText Textual representation of naming authority. + */ + public NamingAuthority(DERObjectIdentifier namingAuthorityId, + String namingAuthorityUrl, DirectoryString namingAuthorityText) + { + this.namingAuthorityId = namingAuthorityId; + this.namingAuthorityUrl = namingAuthorityUrl; + this.namingAuthorityText = namingAuthorityText; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+ * Returns: + *

+ *

+     *             NamingAuthority ::= SEQUENCE
+     *             {
+     *               namingAuthorityId OBJECT IDENTIFIER OPTIONAL,
+     *               namingAuthorityUrl IA5String OPTIONAL,
+     *               namingAuthorityText DirectoryString(SIZE(1..128)) OPTIONAL
+     *             }
+     * 
+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + ASN1EncodableVector vec = new ASN1EncodableVector(); + if (namingAuthorityId != null) + { + vec.add(namingAuthorityId); + } + if (namingAuthorityUrl != null) + { + vec.add(new DERIA5String(namingAuthorityUrl, true)); + } + if (namingAuthorityText != null) + { + vec.add(namingAuthorityText); + } + return new DERSequence(vec); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/ProcurationSyntax.java b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/ProcurationSyntax.java new file mode 100644 index 000000000..3f5f3bd75 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/ProcurationSyntax.java @@ -0,0 +1,240 @@ +package com.google.bitcoin.bouncycastle.asn1.isismtt.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERPrintableString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x500.DirectoryString; +import com.google.bitcoin.bouncycastle.asn1.x509.GeneralName; +import com.google.bitcoin.bouncycastle.asn1.x509.IssuerSerial; + +import java.util.Enumeration; + +/** + * Attribute to indicate that the certificate holder may sign in the name of a + * third person. + *

+ * ISIS-MTT PROFILE: The corresponding ProcurationSyntax contains either the + * name of the person who is represented (subcomponent thirdPerson) or a + * reference to his/her base certificate (in the component signingFor, + * subcomponent certRef), furthermore the optional components country and + * typeSubstitution to indicate the country whose laws apply, and respectively + * the type of procuration (e.g. manager, procuration, custody). + *

+ * ISIS-MTT PROFILE: The GeneralName MUST be of type directoryName and MAY only + * contain: - RFC3039 attributes, except pseudonym (countryName, commonName, + * surname, givenName, serialNumber, organizationName, organizationalUnitName, + * stateOrProvincename, localityName, postalAddress) and - SubjectDirectoryName + * attributes (title, dateOfBirth, placeOfBirth, gender, countryOfCitizenship, + * countryOfResidence and NameAtBirth). + * + *

+ *               ProcurationSyntax ::= SEQUENCE {
+ *                 country [1] EXPLICIT PrintableString(SIZE(2)) OPTIONAL,
+ *                 typeOfSubstitution [2] EXPLICIT DirectoryString (SIZE(1..128)) OPTIONAL,
+ *                 signingFor [3] EXPLICIT SigningFor 
+ *               }
+ *               
+ *               SigningFor ::= CHOICE 
+ *               { 
+ *                 thirdPerson GeneralName,
+ *                 certRef IssuerSerial 
+ *               }
+ * 
+ * + */ +public class ProcurationSyntax + extends ASN1Encodable +{ + private String country; + private DirectoryString typeOfSubstitution; + + private GeneralName thirdPerson; + private IssuerSerial certRef; + + public static ProcurationSyntax getInstance(Object obj) + { + if (obj == null || obj instanceof ProcurationSyntax) + { + return (ProcurationSyntax)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new ProcurationSyntax((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + + obj.getClass().getName()); + } + + /** + * Constructor from ASN1Sequence. + *

+ * The sequence is of type ProcurationSyntax: + *

+ *

+     *               ProcurationSyntax ::= SEQUENCE {
+     *                 country [1] EXPLICIT PrintableString(SIZE(2)) OPTIONAL,
+     *                 typeOfSubstitution [2] EXPLICIT DirectoryString (SIZE(1..128)) OPTIONAL,
+     *                 signingFor [3] EXPLICIT SigningFor
+     *               }
+     * 

+ * SigningFor ::= CHOICE + * { + * thirdPerson GeneralName, + * certRef IssuerSerial + * } + *

+ * + * @param seq The ASN.1 sequence. + */ + private ProcurationSyntax(ASN1Sequence seq) + { + if (seq.size() < 1 || seq.size() > 3) + { + throw new IllegalArgumentException("Bad sequence size: " + seq.size()); + } + Enumeration e = seq.getObjects(); + + while (e.hasMoreElements()) + { + ASN1TaggedObject o = ASN1TaggedObject.getInstance(e.nextElement()); + switch (o.getTagNo()) + { + case 1: + country = DERPrintableString.getInstance(o, true).getString(); + break; + case 2: + typeOfSubstitution = DirectoryString.getInstance(o, true); + break; + case 3: + DEREncodable signingFor = o.getObject(); + if (signingFor instanceof ASN1TaggedObject) + { + thirdPerson = GeneralName.getInstance(signingFor); + } + else + { + certRef = IssuerSerial.getInstance(signingFor); + } + break; + default: + throw new IllegalArgumentException("Bad tag number: " + o.getTagNo()); + } + } + } + + /** + * Constructor from a given details. + *

+ *

+ * Either generalName or certRef MUST be + * null. + * + * @param country The country code whose laws apply. + * @param typeOfSubstitution The type of procuration. + * @param certRef Reference to certificate of the person who is represented. + */ + public ProcurationSyntax( + String country, + DirectoryString typeOfSubstitution, + IssuerSerial certRef) + { + this.country = country; + this.typeOfSubstitution = typeOfSubstitution; + this.thirdPerson = null; + this.certRef = certRef; + } + + /** + * Constructor from a given details. + *

+ *

+ * Either generalName or certRef MUST be + * null. + * + * @param country The country code whose laws apply. + * @param typeOfSubstitution The type of procuration. + * @param thirdPerson The GeneralName of the person who is represented. + */ + public ProcurationSyntax( + String country, + DirectoryString typeOfSubstitution, + GeneralName thirdPerson) + { + this.country = country; + this.typeOfSubstitution = typeOfSubstitution; + this.thirdPerson = thirdPerson; + this.certRef = null; + } + + public String getCountry() + { + return country; + } + + public DirectoryString getTypeOfSubstitution() + { + return typeOfSubstitution; + } + + public GeneralName getThirdPerson() + { + return thirdPerson; + } + + public IssuerSerial getCertRef() + { + return certRef; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+ * Returns: + *

+ *

+     *               ProcurationSyntax ::= SEQUENCE {
+     *                 country [1] EXPLICIT PrintableString(SIZE(2)) OPTIONAL,
+     *                 typeOfSubstitution [2] EXPLICIT DirectoryString (SIZE(1..128)) OPTIONAL,
+     *                 signingFor [3] EXPLICIT SigningFor
+     *               }
+     * 

+ * SigningFor ::= CHOICE + * { + * thirdPerson GeneralName, + * certRef IssuerSerial + * } + *

+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + ASN1EncodableVector vec = new ASN1EncodableVector(); + if (country != null) + { + vec.add(new DERTaggedObject(true, 1, new DERPrintableString(country, true))); + } + if (typeOfSubstitution != null) + { + vec.add(new DERTaggedObject(true, 2, typeOfSubstitution)); + } + if (thirdPerson != null) + { + vec.add(new DERTaggedObject(true, 3, thirdPerson)); + } + else + { + vec.add(new DERTaggedObject(true, 3, certRef)); + } + + return new DERSequence(vec); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/ProfessionInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/ProfessionInfo.java new file mode 100644 index 000000000..0f4d96bbd --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/ProfessionInfo.java @@ -0,0 +1,407 @@ +package com.google.bitcoin.bouncycastle.asn1.isismtt.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERPrintableString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x500.DirectoryString; + +import java.util.Enumeration; + +/** + * Professions, specializations, disciplines, fields of activity, etc. + * + *
+ *               ProfessionInfo ::= SEQUENCE 
+ *               {
+ *                 namingAuthority [0] EXPLICIT NamingAuthority OPTIONAL,
+ *                 professionItems SEQUENCE OF DirectoryString (SIZE(1..128)),
+ *                 professionOIDs SEQUENCE OF OBJECT IDENTIFIER OPTIONAL,
+ *                 registrationNumber PrintableString(SIZE(1..128)) OPTIONAL,
+ *                 addProfessionInfo OCTET STRING OPTIONAL 
+ *               }
+ * 
+ * + * @see com.google.bitcoin.bouncycastle.asn1.isismtt.x509.AdmissionSyntax + */ +public class ProfessionInfo extends ASN1Encodable +{ + + /** + * Rechtsanw�ltin + */ + public static final DERObjectIdentifier Rechtsanwltin = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".1"); + + /** + * Rechtsanwalt + */ + public static final DERObjectIdentifier Rechtsanwalt = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".2"); + + /** + * Rechtsbeistand + */ + public static final DERObjectIdentifier Rechtsbeistand = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".3"); + + /** + * Steuerberaterin + */ + public static final DERObjectIdentifier Steuerberaterin = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".4"); + + /** + * Steuerberater + */ + public static final DERObjectIdentifier Steuerberater = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".5"); + + /** + * Steuerbevollm�chtigte + */ + public static final DERObjectIdentifier Steuerbevollmchtigte = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".6"); + + /** + * Steuerbevollm�chtigter + */ + public static final DERObjectIdentifier Steuerbevollmchtigter = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".7"); + + /** + * Notarin + */ + public static final DERObjectIdentifier Notarin = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".8"); + + /** + * Notar + */ + public static final DERObjectIdentifier Notar = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".9"); + + /** + * Notarvertreterin + */ + public static final DERObjectIdentifier Notarvertreterin = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".10"); + + /** + * Notarvertreter + */ + public static final DERObjectIdentifier Notarvertreter = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".11"); + + /** + * Notariatsverwalterin + */ + public static final DERObjectIdentifier Notariatsverwalterin = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".12"); + + /** + * Notariatsverwalter + */ + public static final DERObjectIdentifier Notariatsverwalter = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".13"); + + /** + * Wirtschaftspr�ferin + */ + public static final DERObjectIdentifier Wirtschaftsprferin = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".14"); + + /** + * Wirtschaftspr�fer + */ + public static final DERObjectIdentifier Wirtschaftsprfer = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".15"); + + /** + * Vereidigte Buchpr�ferin + */ + public static final DERObjectIdentifier VereidigteBuchprferin = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".16"); + + /** + * Vereidigter Buchpr�fer + */ + public static final DERObjectIdentifier VereidigterBuchprfer = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".17"); + + /** + * Patentanw�ltin + */ + public static final DERObjectIdentifier Patentanwltin = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".18"); + + /** + * Patentanwalt + */ + public static final DERObjectIdentifier Patentanwalt = new DERObjectIdentifier( + NamingAuthority.id_isismtt_at_namingAuthorities_RechtWirtschaftSteuern + ".19"); + + private NamingAuthority namingAuthority; + + private ASN1Sequence professionItems; + + private ASN1Sequence professionOIDs; + + private String registrationNumber; + + private ASN1OctetString addProfessionInfo; + + public static ProfessionInfo getInstance(Object obj) + { + if (obj == null || obj instanceof ProfessionInfo) + { + return (ProfessionInfo)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new ProfessionInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + + obj.getClass().getName()); + } + + /** + * Constructor from ASN1Sequence. + *

+ *

+ *

+     *               ProfessionInfo ::= SEQUENCE
+     *               {
+     *                 namingAuthority [0] EXPLICIT NamingAuthority OPTIONAL,
+     *                 professionItems SEQUENCE OF DirectoryString (SIZE(1..128)),
+     *                 professionOIDs SEQUENCE OF OBJECT IDENTIFIER OPTIONAL,
+     *                 registrationNumber PrintableString(SIZE(1..128)) OPTIONAL,
+     *                 addProfessionInfo OCTET STRING OPTIONAL
+     *               }
+     * 
+ * + * @param seq The ASN.1 sequence. + */ + private ProfessionInfo(ASN1Sequence seq) + { + if (seq.size() > 5) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + Enumeration e = seq.getObjects(); + + DEREncodable o = (DEREncodable)e.nextElement(); + + if (o instanceof ASN1TaggedObject) + { + if (((ASN1TaggedObject)o).getTagNo() != 0) + { + throw new IllegalArgumentException("Bad tag number: " + + ((ASN1TaggedObject)o).getTagNo()); + } + namingAuthority = NamingAuthority.getInstance((ASN1TaggedObject)o, true); + o = (DEREncodable)e.nextElement(); + } + + professionItems = ASN1Sequence.getInstance(o); + + if (e.hasMoreElements()) + { + o = (DEREncodable)e.nextElement(); + if (o instanceof ASN1Sequence) + { + professionOIDs = ASN1Sequence.getInstance(o); + } + else if (o instanceof DERPrintableString) + { + registrationNumber = DERPrintableString.getInstance(o).getString(); + } + else if (o instanceof ASN1OctetString) + { + addProfessionInfo = ASN1OctetString.getInstance(o); + } + else + { + throw new IllegalArgumentException("Bad object encountered: " + + o.getClass()); + } + } + if (e.hasMoreElements()) + { + o = (DEREncodable)e.nextElement(); + if (o instanceof DERPrintableString) + { + registrationNumber = DERPrintableString.getInstance(o).getString(); + } + else if (o instanceof DEROctetString) + { + addProfessionInfo = (DEROctetString)o; + } + else + { + throw new IllegalArgumentException("Bad object encountered: " + + o.getClass()); + } + } + if (e.hasMoreElements()) + { + o = (DEREncodable)e.nextElement(); + if (o instanceof DEROctetString) + { + addProfessionInfo = (DEROctetString)o; + } + else + { + throw new IllegalArgumentException("Bad object encountered: " + + o.getClass()); + } + } + + } + + /** + * Constructor from given details. + *

+ * professionItems is mandatory, all other parameters are + * optional. + * + * @param namingAuthority The naming authority. + * @param professionItems Directory strings of the profession. + * @param professionOIDs DERObjectIdentfier objects for the + * profession. + * @param registrationNumber Registration number. + * @param addProfessionInfo Additional infos in encoded form. + */ + public ProfessionInfo(NamingAuthority namingAuthority, + DirectoryString[] professionItems, DERObjectIdentifier[] professionOIDs, + String registrationNumber, ASN1OctetString addProfessionInfo) + { + this.namingAuthority = namingAuthority; + ASN1EncodableVector v = new ASN1EncodableVector(); + for (int i = 0; i != professionItems.length; i++) + { + v.add(professionItems[i]); + } + this.professionItems = new DERSequence(v); + if (professionOIDs != null) + { + v = new ASN1EncodableVector(); + for (int i = 0; i != professionOIDs.length; i++) + { + v.add(professionOIDs[i]); + } + this.professionOIDs = new DERSequence(v); + } + this.registrationNumber = registrationNumber; + this.addProfessionInfo = addProfessionInfo; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+ * Returns: + *

+ *

+     *               ProfessionInfo ::= SEQUENCE
+     *               {
+     *                 namingAuthority [0] EXPLICIT NamingAuthority OPTIONAL,
+     *                 professionItems SEQUENCE OF DirectoryString (SIZE(1..128)),
+     *                 professionOIDs SEQUENCE OF OBJECT IDENTIFIER OPTIONAL,
+     *                 registrationNumber PrintableString(SIZE(1..128)) OPTIONAL,
+     *                 addProfessionInfo OCTET STRING OPTIONAL
+     *               }
+     * 
+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + ASN1EncodableVector vec = new ASN1EncodableVector(); + if (namingAuthority != null) + { + vec.add(new DERTaggedObject(true, 0, namingAuthority)); + } + vec.add(professionItems); + if (professionOIDs != null) + { + vec.add(professionOIDs); + } + if (registrationNumber != null) + { + vec.add(new DERPrintableString(registrationNumber, true)); + } + if (addProfessionInfo != null) + { + vec.add(addProfessionInfo); + } + return new DERSequence(vec); + } + + /** + * @return Returns the addProfessionInfo. + */ + public ASN1OctetString getAddProfessionInfo() + { + return addProfessionInfo; + } + + /** + * @return Returns the namingAuthority. + */ + public NamingAuthority getNamingAuthority() + { + return namingAuthority; + } + + /** + * @return Returns the professionItems. + */ + public DirectoryString[] getProfessionItems() + { + DirectoryString[] items = new DirectoryString[professionItems.size()]; + int count = 0; + for (Enumeration e = professionItems.getObjects(); e.hasMoreElements();) + { + items[count++] = DirectoryString.getInstance(e.nextElement()); + } + return items; + } + + /** + * @return Returns the professionOIDs. + */ + public DERObjectIdentifier[] getProfessionOIDs() + { + if (professionOIDs == null) + { + return new DERObjectIdentifier[0]; + } + DERObjectIdentifier[] oids = new DERObjectIdentifier[professionOIDs.size()]; + int count = 0; + for (Enumeration e = professionOIDs.getObjects(); e.hasMoreElements();) + { + oids[count++] = DERObjectIdentifier.getInstance(e.nextElement()); + } + return oids; + } + + /** + * @return Returns the registrationNumber. + */ + public String getRegistrationNumber() + { + return registrationNumber; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/Restriction.java b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/Restriction.java new file mode 100644 index 000000000..e15fe46df --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/isismtt/x509/Restriction.java @@ -0,0 +1,82 @@ +package com.google.bitcoin.bouncycastle.asn1.isismtt.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERString; +import com.google.bitcoin.bouncycastle.asn1.x500.DirectoryString; + +/** + * Some other restriction regarding the usage of this certificate. + *

+ *

+ *  RestrictionSyntax ::= DirectoryString (SIZE(1..1024))
+ * 
+ */ +public class Restriction extends ASN1Encodable +{ + private DirectoryString restriction; + + public static Restriction getInstance(Object obj) + { + if (obj == null || obj instanceof Restriction) + { + return (Restriction)obj; + } + + if (obj instanceof DERString) + { + return new Restriction(DirectoryString.getInstance(obj)); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + + obj.getClass().getName()); + } + + /** + * Constructor from DERString. + *

+ * The DERString is of type RestrictionSyntax: + *

+ *

+     *      RestrictionSyntax ::= DirectoryString (SIZE(1..1024))
+     * 
+ * + * @param restriction A DERString. + */ + private Restriction(DirectoryString restriction) + { + this.restriction = restriction; + } + + /** + * Constructor from a given details. + * + * @param restriction The describtion of the restriction. + */ + public Restriction(String restriction) + { + this.restriction = new DirectoryString(restriction); + } + + public DirectoryString getRestriction() + { + return restriction; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+ * Returns: + *

+ *

+     *      RestrictionSyntax ::= DirectoryString (SIZE(1..1024))
+     * 

+ *

+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + return restriction.toASN1Object(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/kisa/KISAObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/kisa/KISAObjectIdentifiers.java new file mode 100644 index 000000000..4645e0986 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/kisa/KISAObjectIdentifiers.java @@ -0,0 +1,9 @@ +package com.google.bitcoin.bouncycastle.asn1.kisa; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface KISAObjectIdentifiers +{ + public static final DERObjectIdentifier id_seedCBC = new DERObjectIdentifier("1.2.410.200004.1.4"); + public static final DERObjectIdentifier id_npki_app_cmsSeed_wrap = new DERObjectIdentifier("1.2.410.200004.7.1.1.1"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/microsoft/MicrosoftObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/microsoft/MicrosoftObjectIdentifiers.java new file mode 100644 index 000000000..f54106ef5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/microsoft/MicrosoftObjectIdentifiers.java @@ -0,0 +1,17 @@ +package com.google.bitcoin.bouncycastle.asn1.microsoft; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface MicrosoftObjectIdentifiers +{ + // + // Microsoft + // iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) microsoft(311) + // + static final DERObjectIdentifier microsoft = new DERObjectIdentifier("1.3.6.1.4.1.311"); + static final DERObjectIdentifier microsoftCertTemplateV1 = new DERObjectIdentifier(microsoft + ".20.2"); + static final DERObjectIdentifier microsoftCaVersion = new DERObjectIdentifier(microsoft + ".21.1"); + static final DERObjectIdentifier microsoftPrevCaCertHash = new DERObjectIdentifier(microsoft + ".21.2"); + static final DERObjectIdentifier microsoftCertTemplateV2 = new DERObjectIdentifier(microsoft + ".21.7"); + static final DERObjectIdentifier microsoftAppPolicies = new DERObjectIdentifier(microsoft + ".21.10"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/misc/CAST5CBCParameters.java b/src/com/google/bitcoin/bouncycastle/asn1/misc/CAST5CBCParameters.java new file mode 100644 index 000000000..6d297e0a6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/misc/CAST5CBCParameters.java @@ -0,0 +1,71 @@ +package com.google.bitcoin.bouncycastle.asn1.misc; + +import com.google.bitcoin.bouncycastle.asn1.*; + +public class CAST5CBCParameters + extends ASN1Encodable +{ + DERInteger keyLength; + ASN1OctetString iv; + + public static CAST5CBCParameters getInstance( + Object o) + { + if (o instanceof CAST5CBCParameters) + { + return (CAST5CBCParameters)o; + } + else if (o instanceof ASN1Sequence) + { + return new CAST5CBCParameters((ASN1Sequence)o); + } + + throw new IllegalArgumentException("unknown object in CAST5CBCParameter factory"); + } + + public CAST5CBCParameters( + byte[] iv, + int keyLength) + { + this.iv = new DEROctetString(iv); + this.keyLength = new DERInteger(keyLength); + } + + public CAST5CBCParameters( + ASN1Sequence seq) + { + iv = (ASN1OctetString)seq.getObjectAt(0); + keyLength = (DERInteger)seq.getObjectAt(1); + } + + public byte[] getIV() + { + return iv.getOctets(); + } + + public int getKeyLength() + { + return keyLength.getValue().intValue(); + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * cast5CBCParameters ::= SEQUENCE {
+     *                           iv         OCTET STRING DEFAULT 0,
+     *                                  -- Initialization vector
+     *                           keyLength  INTEGER
+     *                                  -- Key length, in bits
+     *                      }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(iv); + v.add(keyLength); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/misc/IDEACBCPar.java b/src/com/google/bitcoin/bouncycastle/asn1/misc/IDEACBCPar.java new file mode 100644 index 000000000..8d574dbcf --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/misc/IDEACBCPar.java @@ -0,0 +1,75 @@ +package com.google.bitcoin.bouncycastle.asn1.misc; + +import com.google.bitcoin.bouncycastle.asn1.*; + +public class IDEACBCPar + extends ASN1Encodable +{ + ASN1OctetString iv; + + public static IDEACBCPar getInstance( + Object o) + { + if (o instanceof IDEACBCPar) + { + return (IDEACBCPar)o; + } + else if (o instanceof ASN1Sequence) + { + return new IDEACBCPar((ASN1Sequence)o); + } + + throw new IllegalArgumentException("unknown object in IDEACBCPar factory"); + } + + public IDEACBCPar( + byte[] iv) + { + this.iv = new DEROctetString(iv); + } + + public IDEACBCPar( + ASN1Sequence seq) + { + if (seq.size() == 1) + { + iv = (ASN1OctetString)seq.getObjectAt(0); + } + else + { + iv = null; + } + } + + public byte[] getIV() + { + if (iv != null) + { + return iv.getOctets(); + } + else + { + return null; + } + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * IDEA-CBCPar ::= SEQUENCE {
+     *                      iv    OCTET STRING OPTIONAL -- exactly 8 octets
+     *                  }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (iv != null) + { + v.add(iv); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/misc/MiscObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/misc/MiscObjectIdentifiers.java new file mode 100644 index 000000000..25a79b610 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/misc/MiscObjectIdentifiers.java @@ -0,0 +1,46 @@ +package com.google.bitcoin.bouncycastle.asn1.misc; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface MiscObjectIdentifiers +{ + // + // Netscape + // iso/itu(2) joint-assign(16) us(840) uscompany(1) netscape(113730) cert-extensions(1) } + // + static final String netscape = "2.16.840.1.113730.1"; + static final DERObjectIdentifier netscapeCertType = new DERObjectIdentifier(netscape + ".1"); + static final DERObjectIdentifier netscapeBaseURL = new DERObjectIdentifier(netscape + ".2"); + static final DERObjectIdentifier netscapeRevocationURL = new DERObjectIdentifier(netscape + ".3"); + static final DERObjectIdentifier netscapeCARevocationURL = new DERObjectIdentifier(netscape + ".4"); + static final DERObjectIdentifier netscapeRenewalURL = new DERObjectIdentifier(netscape + ".7"); + static final DERObjectIdentifier netscapeCApolicyURL = new DERObjectIdentifier(netscape + ".8"); + static final DERObjectIdentifier netscapeSSLServerName = new DERObjectIdentifier(netscape + ".12"); + static final DERObjectIdentifier netscapeCertComment = new DERObjectIdentifier(netscape + ".13"); + // + // Verisign + // iso/itu(2) joint-assign(16) us(840) uscompany(1) verisign(113733) cert-extensions(1) } + // + static final String verisign = "2.16.840.1.113733.1"; + + // + // CZAG - country, zip, age, and gender + // + static final DERObjectIdentifier verisignCzagExtension = new DERObjectIdentifier(verisign + ".6.3"); + // D&B D-U-N-S number + static final DERObjectIdentifier verisignDnbDunsNumber = new DERObjectIdentifier(verisign + ".6.15"); + + // + // Novell + // iso/itu(2) country(16) us(840) organization(1) novell(113719) + // + static final String novell = "2.16.840.1.113719"; + static final DERObjectIdentifier novellSecurityAttribs = new DERObjectIdentifier(novell + ".1.9.4.1"); + + // + // Entrust + // iso(1) member-body(16) us(840) nortelnetworks(113533) entrust(7) + // + static final String entrust = "1.2.840.113533.7"; + static final DERObjectIdentifier entrustVersionExtension = new DERObjectIdentifier(entrust + ".65.0"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/misc/NetscapeCertType.java b/src/com/google/bitcoin/bouncycastle/asn1/misc/NetscapeCertType.java new file mode 100644 index 000000000..286616bd6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/misc/NetscapeCertType.java @@ -0,0 +1,54 @@ +package com.google.bitcoin.bouncycastle.asn1.misc; + +import com.google.bitcoin.bouncycastle.asn1.*; + +/** + * The NetscapeCertType object. + *
+ *    NetscapeCertType ::= BIT STRING {
+ *         SSLClient               (0),
+ *         SSLServer               (1),
+ *         S/MIME                  (2),
+ *         Object Signing          (3),
+ *         Reserved                (4),
+ *         SSL CA                  (5),
+ *         S/MIME CA               (6),
+ *         Object Signing CA       (7) }
+ * 
+ */ +public class NetscapeCertType + extends DERBitString +{ + public static final int sslClient = (1 << 7); + public static final int sslServer = (1 << 6); + public static final int smime = (1 << 5); + public static final int objectSigning = (1 << 4); + public static final int reserved = (1 << 3); + public static final int sslCA = (1 << 2); + public static final int smimeCA = (1 << 1); + public static final int objectSigningCA = (1 << 0); + + /** + * Basic constructor. + * + * @param usage - the bitwise OR of the Key Usage flags giving the + * allowed uses for the key. + * e.g. (X509NetscapeCertType.sslCA | X509NetscapeCertType.smimeCA) + */ + public NetscapeCertType( + int usage) + { + super(getBytes(usage), getPadBits(usage)); + } + + public NetscapeCertType( + DERBitString usage) + { + super(usage.getBytes(), usage.getPadBits()); + } + + public String toString() + { + return "NetscapeCertType: 0x" + Integer.toHexString(data[0] & 0xff); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/misc/NetscapeRevocationURL.java b/src/com/google/bitcoin/bouncycastle/asn1/misc/NetscapeRevocationURL.java new file mode 100644 index 000000000..4ab5e858b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/misc/NetscapeRevocationURL.java @@ -0,0 +1,18 @@ +package com.google.bitcoin.bouncycastle.asn1.misc; + +import com.google.bitcoin.bouncycastle.asn1.*; + +public class NetscapeRevocationURL + extends DERIA5String +{ + public NetscapeRevocationURL( + DERIA5String str) + { + super(str.getString()); + } + + public String toString() + { + return "NetscapeRevocationURL: " + this.getString(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/misc/VerisignCzagExtension.java b/src/com/google/bitcoin/bouncycastle/asn1/misc/VerisignCzagExtension.java new file mode 100644 index 000000000..25cc154d2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/misc/VerisignCzagExtension.java @@ -0,0 +1,18 @@ +package com.google.bitcoin.bouncycastle.asn1.misc; + +import com.google.bitcoin.bouncycastle.asn1.*; + +public class VerisignCzagExtension + extends DERIA5String +{ + public VerisignCzagExtension( + DERIA5String str) + { + super(str.getString()); + } + + public String toString() + { + return "VerisignCzagExtension: " + this.getString(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/mozilla/PublicKeyAndChallenge.java b/src/com/google/bitcoin/bouncycastle/asn1/mozilla/PublicKeyAndChallenge.java new file mode 100644 index 000000000..e16cbae4a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/mozilla/PublicKeyAndChallenge.java @@ -0,0 +1,63 @@ +package com.google.bitcoin.bouncycastle.asn1.mozilla; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERIA5String; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.x509.SubjectPublicKeyInfo; + +/** + * This is designed to parse + * the PublicKeyAndChallenge created by the KEYGEN tag included by + * Mozilla based browsers. + *
+ *  PublicKeyAndChallenge ::= SEQUENCE {
+ *    spki SubjectPublicKeyInfo,
+ *    challenge IA5STRING
+ *  }
+ *
+ *  
+ */ +public class PublicKeyAndChallenge + extends ASN1Encodable +{ + private ASN1Sequence pkacSeq; + private SubjectPublicKeyInfo spki; + private DERIA5String challenge; + + public static PublicKeyAndChallenge getInstance(Object obj) + { + if (obj instanceof PublicKeyAndChallenge) + { + return (PublicKeyAndChallenge)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new PublicKeyAndChallenge((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unkown object in factory: " + obj.getClass().getName()); + } + + public PublicKeyAndChallenge(ASN1Sequence seq) + { + pkacSeq = seq; + spki = SubjectPublicKeyInfo.getInstance(seq.getObjectAt(0)); + challenge = DERIA5String.getInstance(seq.getObjectAt(1)); + } + + public DERObject toASN1Object() + { + return pkacSeq; + } + + public SubjectPublicKeyInfo getSubjectPublicKeyInfo() + { + return spki; + } + + public DERIA5String getChallenge() + { + return challenge; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/nist/NISTNamedCurves.java b/src/com/google/bitcoin/bouncycastle/asn1/nist/NISTNamedCurves.java new file mode 100644 index 000000000..717bf62c3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/nist/NISTNamedCurves.java @@ -0,0 +1,96 @@ +package com.google.bitcoin.bouncycastle.asn1.nist; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.sec.SECNamedCurves; +import com.google.bitcoin.bouncycastle.asn1.sec.SECObjectIdentifiers; +import com.google.bitcoin.bouncycastle.asn1.x9.X9ECParameters; +import com.google.bitcoin.bouncycastle.util.Strings; + +import java.util.Enumeration; +import java.util.Hashtable; + +/** + * Utility class for fetching curves using their NIST names as published in FIPS-PUB 186-2 + */ +public class NISTNamedCurves +{ + static final Hashtable objIds = new Hashtable(); + static final Hashtable names = new Hashtable(); + + static void defineCurve(String name, DERObjectIdentifier oid) + { + objIds.put(name, oid); + names.put(oid, name); + } + + static + { + // TODO Missing the "K-" curves + + defineCurve("B-571", SECObjectIdentifiers.sect571r1); + defineCurve("B-409", SECObjectIdentifiers.sect409r1); + defineCurve("B-283", SECObjectIdentifiers.sect283r1); + defineCurve("B-233", SECObjectIdentifiers.sect233r1); + defineCurve("B-163", SECObjectIdentifiers.sect163r2); + defineCurve("P-521", SECObjectIdentifiers.secp521r1); + defineCurve("P-384", SECObjectIdentifiers.secp384r1); + defineCurve("P-256", SECObjectIdentifiers.secp256r1); + defineCurve("P-224", SECObjectIdentifiers.secp224r1); + defineCurve("P-192", SECObjectIdentifiers.secp192r1); + } + + public static X9ECParameters getByName( + String name) + { + DERObjectIdentifier oid = (DERObjectIdentifier)objIds.get(Strings.toUpperCase(name)); + + if (oid != null) + { + return getByOID(oid); + } + + return null; + } + + /** + * return the X9ECParameters object for the named curve represented by + * the passed in object identifier. Null if the curve isn't present. + * + * @param oid an object identifier representing a named curve, if present. + */ + public static X9ECParameters getByOID( + DERObjectIdentifier oid) + { + return SECNamedCurves.getByOID(oid); + } + + /** + * return the object identifier signified by the passed in name. Null + * if there is no object identifier associated with name. + * + * @return the object identifier associated with name, if present. + */ + public static DERObjectIdentifier getOID( + String name) + { + return (DERObjectIdentifier)objIds.get(Strings.toUpperCase(name)); + } + + /** + * return the named curve name represented by the given object identifier. + */ + public static String getName( + DERObjectIdentifier oid) + { + return (String)names.get(oid); + } + + /** + * returns an enumeration containing the name strings for curves + * contained in this structure. + */ + public static Enumeration getNames() + { + return objIds.keys(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/nist/NISTObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/nist/NISTObjectIdentifiers.java new file mode 100644 index 000000000..3487aaeab --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/nist/NISTObjectIdentifiers.java @@ -0,0 +1,56 @@ +package com.google.bitcoin.bouncycastle.asn1.nist; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface NISTObjectIdentifiers +{ + // + // NIST + // iso/itu(2) joint-assign(16) us(840) organization(1) gov(101) csor(3) + + // + // nistalgorithms(4) + // + static final String nistAlgorithm = "2.16.840.1.101.3.4"; + + static final DERObjectIdentifier id_sha256 = new DERObjectIdentifier(nistAlgorithm + ".2.1"); + static final DERObjectIdentifier id_sha384 = new DERObjectIdentifier(nistAlgorithm + ".2.2"); + static final DERObjectIdentifier id_sha512 = new DERObjectIdentifier(nistAlgorithm + ".2.3"); + static final DERObjectIdentifier id_sha224 = new DERObjectIdentifier(nistAlgorithm + ".2.4"); + + static final String aes = nistAlgorithm + ".1"; + + static final DERObjectIdentifier id_aes128_ECB = new DERObjectIdentifier(aes + ".1"); + static final DERObjectIdentifier id_aes128_CBC = new DERObjectIdentifier(aes + ".2"); + static final DERObjectIdentifier id_aes128_OFB = new DERObjectIdentifier(aes + ".3"); + static final DERObjectIdentifier id_aes128_CFB = new DERObjectIdentifier(aes + ".4"); + static final DERObjectIdentifier id_aes128_wrap = new DERObjectIdentifier(aes + ".5"); + static final DERObjectIdentifier id_aes128_GCM = new DERObjectIdentifier(aes + ".6"); + static final DERObjectIdentifier id_aes128_CCM = new DERObjectIdentifier(aes + ".7"); + + static final DERObjectIdentifier id_aes192_ECB = new DERObjectIdentifier(aes + ".21"); + static final DERObjectIdentifier id_aes192_CBC = new DERObjectIdentifier(aes + ".22"); + static final DERObjectIdentifier id_aes192_OFB = new DERObjectIdentifier(aes + ".23"); + static final DERObjectIdentifier id_aes192_CFB = new DERObjectIdentifier(aes + ".24"); + static final DERObjectIdentifier id_aes192_wrap = new DERObjectIdentifier(aes + ".25"); + static final DERObjectIdentifier id_aes192_GCM = new DERObjectIdentifier(aes + ".26"); + static final DERObjectIdentifier id_aes192_CCM = new DERObjectIdentifier(aes + ".27"); + + static final DERObjectIdentifier id_aes256_ECB = new DERObjectIdentifier(aes + ".41"); + static final DERObjectIdentifier id_aes256_CBC = new DERObjectIdentifier(aes + ".42"); + static final DERObjectIdentifier id_aes256_OFB = new DERObjectIdentifier(aes + ".43"); + static final DERObjectIdentifier id_aes256_CFB = new DERObjectIdentifier(aes + ".44"); + static final DERObjectIdentifier id_aes256_wrap = new DERObjectIdentifier(aes + ".45"); + static final DERObjectIdentifier id_aes256_GCM = new DERObjectIdentifier(aes + ".46"); + static final DERObjectIdentifier id_aes256_CCM = new DERObjectIdentifier(aes + ".47"); + + // + // signatures + // + static final DERObjectIdentifier id_dsa_with_sha2 = new DERObjectIdentifier(nistAlgorithm + ".3"); + + static final DERObjectIdentifier dsa_with_sha224 = new DERObjectIdentifier(id_dsa_with_sha2 + ".1"); + static final DERObjectIdentifier dsa_with_sha256 = new DERObjectIdentifier(id_dsa_with_sha2 + ".2"); + static final DERObjectIdentifier dsa_with_sha384 = new DERObjectIdentifier(id_dsa_with_sha2 + ".3"); + static final DERObjectIdentifier dsa_with_sha512 = new DERObjectIdentifier(id_dsa_with_sha2 + ".4"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ntt/NTTObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/ntt/NTTObjectIdentifiers.java new file mode 100644 index 000000000..17c31e0ca --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ntt/NTTObjectIdentifiers.java @@ -0,0 +1,17 @@ +package com.google.bitcoin.bouncycastle.asn1.ntt; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +/** + * From RFC 3657 + */ +public interface NTTObjectIdentifiers +{ + public static final DERObjectIdentifier id_camellia128_cbc = new DERObjectIdentifier("1.2.392.200011.61.1.1.1.2"); + public static final DERObjectIdentifier id_camellia192_cbc = new DERObjectIdentifier("1.2.392.200011.61.1.1.1.3"); + public static final DERObjectIdentifier id_camellia256_cbc = new DERObjectIdentifier("1.2.392.200011.61.1.1.1.4"); + + public static final DERObjectIdentifier id_camellia128_wrap = new DERObjectIdentifier("1.2.392.200011.61.1.1.3.2"); + public static final DERObjectIdentifier id_camellia192_wrap = new DERObjectIdentifier("1.2.392.200011.61.1.1.3.3"); + public static final DERObjectIdentifier id_camellia256_wrap = new DERObjectIdentifier("1.2.392.200011.61.1.1.3.4"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/BasicOCSPResponse.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/BasicOCSPResponse.java new file mode 100644 index 000000000..453ac10d8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/BasicOCSPResponse.java @@ -0,0 +1,112 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class BasicOCSPResponse + extends ASN1Encodable +{ + private ResponseData tbsResponseData; + private AlgorithmIdentifier signatureAlgorithm; + private DERBitString signature; + private ASN1Sequence certs; + + public BasicOCSPResponse( + ResponseData tbsResponseData, + AlgorithmIdentifier signatureAlgorithm, + DERBitString signature, + ASN1Sequence certs) + { + this.tbsResponseData = tbsResponseData; + this.signatureAlgorithm = signatureAlgorithm; + this.signature = signature; + this.certs = certs; + } + + public BasicOCSPResponse( + ASN1Sequence seq) + { + this.tbsResponseData = ResponseData.getInstance(seq.getObjectAt(0)); + this.signatureAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(1)); + this.signature = (DERBitString)seq.getObjectAt(2); + + if (seq.size() > 3) + { + this.certs = ASN1Sequence.getInstance((ASN1TaggedObject)seq.getObjectAt(3), true); + } + } + + public static BasicOCSPResponse getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static BasicOCSPResponse getInstance( + Object obj) + { + if (obj == null || obj instanceof BasicOCSPResponse) + { + return (BasicOCSPResponse)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new BasicOCSPResponse((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public ResponseData getTbsResponseData() + { + return tbsResponseData; + } + + public AlgorithmIdentifier getSignatureAlgorithm() + { + return signatureAlgorithm; + } + + public DERBitString getSignature() + { + return signature; + } + + public ASN1Sequence getCerts() + { + return certs; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * BasicOCSPResponse       ::= SEQUENCE {
+     *      tbsResponseData      ResponseData,
+     *      signatureAlgorithm   AlgorithmIdentifier,
+     *      signature            BIT STRING,
+     *      certs                [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(tbsResponseData); + v.add(signatureAlgorithm); + v.add(signature); + if (certs != null) + { + v.add(new DERTaggedObject(true, 0, certs)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/CertID.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/CertID.java new file mode 100644 index 000000000..70b27b758 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/CertID.java @@ -0,0 +1,105 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class CertID + extends ASN1Encodable +{ + AlgorithmIdentifier hashAlgorithm; + ASN1OctetString issuerNameHash; + ASN1OctetString issuerKeyHash; + DERInteger serialNumber; + + public CertID( + AlgorithmIdentifier hashAlgorithm, + ASN1OctetString issuerNameHash, + ASN1OctetString issuerKeyHash, + DERInteger serialNumber) + { + this.hashAlgorithm = hashAlgorithm; + this.issuerNameHash = issuerNameHash; + this.issuerKeyHash = issuerKeyHash; + this.serialNumber = serialNumber; + } + + public CertID( + ASN1Sequence seq) + { + hashAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(0)); + issuerNameHash = (ASN1OctetString)seq.getObjectAt(1); + issuerKeyHash = (ASN1OctetString)seq.getObjectAt(2); + serialNumber = (DERInteger)seq.getObjectAt(3); + } + + public static CertID getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static CertID getInstance( + Object obj) + { + if (obj == null || obj instanceof CertID) + { + return (CertID)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new CertID((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public AlgorithmIdentifier getHashAlgorithm() + { + return hashAlgorithm; + } + + public ASN1OctetString getIssuerNameHash() + { + return issuerNameHash; + } + + public ASN1OctetString getIssuerKeyHash() + { + return issuerKeyHash; + } + + public DERInteger getSerialNumber() + { + return serialNumber; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * CertID          ::=     SEQUENCE {
+     *     hashAlgorithm       AlgorithmIdentifier,
+     *     issuerNameHash      OCTET STRING, -- Hash of Issuer's DN
+     *     issuerKeyHash       OCTET STRING, -- Hash of Issuers public key
+     *     serialNumber        CertificateSerialNumber }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(hashAlgorithm); + v.add(issuerNameHash); + v.add(issuerKeyHash); + v.add(serialNumber); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/CertStatus.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/CertStatus.java new file mode 100644 index 000000000..a01fd8701 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/CertStatus.java @@ -0,0 +1,105 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERNull; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class CertStatus + extends ASN1Encodable + implements ASN1Choice +{ + private int tagNo; + private DEREncodable value; + + /** + * create a CertStatus object with a tag of zero. + */ + public CertStatus() + { + tagNo = 0; + value = new DERNull(); + } + + public CertStatus( + RevokedInfo info) + { + tagNo = 1; + value = info; + } + + public CertStatus( + int tagNo, + DEREncodable value) + { + this.tagNo = tagNo; + this.value = value; + } + + public CertStatus( + ASN1TaggedObject choice) + { + this.tagNo = choice.getTagNo(); + + switch (choice.getTagNo()) + { + case 0: + value = new DERNull(); + break; + case 1: + value = RevokedInfo.getInstance(choice, false); + break; + case 2: + value = new DERNull(); + } + } + + public static CertStatus getInstance( + Object obj) + { + if (obj == null || obj instanceof CertStatus) + { + return (CertStatus)obj; + } + else if (obj instanceof ASN1TaggedObject) + { + return new CertStatus((ASN1TaggedObject)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public static CertStatus getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); // must be explicitly tagged + } + + public int getTagNo() + { + return tagNo; + } + + public DEREncodable getStatus() + { + return value; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  CertStatus ::= CHOICE {
+     *                  good        [0]     IMPLICIT NULL,
+     *                  revoked     [1]     IMPLICIT RevokedInfo,
+     *                  unknown     [2]     IMPLICIT UnknownInfo }
+     * 
+ */ + public DERObject toASN1Object() + { + return new DERTaggedObject(false, tagNo, value); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/CrlID.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/CrlID.java new file mode 100644 index 000000000..4f73e187a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/CrlID.java @@ -0,0 +1,86 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.*; + +public class CrlID + extends ASN1Encodable +{ + DERIA5String crlUrl; + DERInteger crlNum; + DERGeneralizedTime crlTime; + + public CrlID( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + while (e.hasMoreElements()) + { + ASN1TaggedObject o = (ASN1TaggedObject)e.nextElement(); + + switch (o.getTagNo()) + { + case 0: + crlUrl = DERIA5String.getInstance(o, true); + break; + case 1: + crlNum = DERInteger.getInstance(o, true); + break; + case 2: + crlTime = DERGeneralizedTime.getInstance(o, true); + break; + default: + throw new IllegalArgumentException( + "unknown tag number: " + o.getTagNo()); + } + } + } + + public DERIA5String getCrlUrl() + { + return crlUrl; + } + + public DERInteger getCrlNum() + { + return crlNum; + } + + public DERGeneralizedTime getCrlTime() + { + return crlTime; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * CrlID ::= SEQUENCE {
+     *     crlUrl               [0]     EXPLICIT IA5String OPTIONAL,
+     *     crlNum               [1]     EXPLICIT INTEGER OPTIONAL,
+     *     crlTime              [2]     EXPLICIT GeneralizedTime OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (crlUrl != null) + { + v.add(new DERTaggedObject(true, 0, crlUrl)); + } + + if (crlNum != null) + { + v.add(new DERTaggedObject(true, 1, crlNum)); + } + + if (crlTime != null) + { + v.add(new DERTaggedObject(true, 2, crlTime)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPObjectIdentifiers.java new file mode 100644 index 000000000..6c5e36a24 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPObjectIdentifiers.java @@ -0,0 +1,22 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface OCSPObjectIdentifiers +{ + public static final String pkix_ocsp = "1.3.6.1.5.5.7.48.1"; + + public static final DERObjectIdentifier id_pkix_ocsp = new DERObjectIdentifier(pkix_ocsp); + public static final DERObjectIdentifier id_pkix_ocsp_basic = new DERObjectIdentifier(pkix_ocsp + ".1"); + + // + // extensions + // + public static final DERObjectIdentifier id_pkix_ocsp_nonce = new DERObjectIdentifier(pkix_ocsp + ".2"); + public static final DERObjectIdentifier id_pkix_ocsp_crl = new DERObjectIdentifier(pkix_ocsp + ".3"); + + public static final DERObjectIdentifier id_pkix_ocsp_response = new DERObjectIdentifier(pkix_ocsp + ".4"); + public static final DERObjectIdentifier id_pkix_ocsp_nocheck = new DERObjectIdentifier(pkix_ocsp + ".5"); + public static final DERObjectIdentifier id_pkix_ocsp_archive_cutoff = new DERObjectIdentifier(pkix_ocsp + ".6"); + public static final DERObjectIdentifier id_pkix_ocsp_service_locator = new DERObjectIdentifier(pkix_ocsp + ".7"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPRequest.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPRequest.java new file mode 100644 index 000000000..294d94e7e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPRequest.java @@ -0,0 +1,90 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class OCSPRequest + extends ASN1Encodable +{ + TBSRequest tbsRequest; + Signature optionalSignature; + + public OCSPRequest( + TBSRequest tbsRequest, + Signature optionalSignature) + { + this.tbsRequest = tbsRequest; + this.optionalSignature = optionalSignature; + } + + public OCSPRequest( + ASN1Sequence seq) + { + tbsRequest = TBSRequest.getInstance(seq.getObjectAt(0)); + + if (seq.size() == 2) + { + optionalSignature = Signature.getInstance( + (ASN1TaggedObject)seq.getObjectAt(1), true); + } + } + + public static OCSPRequest getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static OCSPRequest getInstance( + Object obj) + { + if (obj == null || obj instanceof OCSPRequest) + { + return (OCSPRequest)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new OCSPRequest((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public TBSRequest getTbsRequest() + { + return tbsRequest; + } + + public Signature getOptionalSignature() + { + return optionalSignature; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * OCSPRequest     ::=     SEQUENCE {
+     *     tbsRequest                  TBSRequest,
+     *     optionalSignature   [0]     EXPLICIT Signature OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(tbsRequest); + + if (optionalSignature != null) + { + v.add(new DERTaggedObject(true, 0, optionalSignature)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPResponse.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPResponse.java new file mode 100644 index 000000000..7ce6469da --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPResponse.java @@ -0,0 +1,92 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREnumerated; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class OCSPResponse + extends ASN1Encodable +{ + OCSPResponseStatus responseStatus; + ResponseBytes responseBytes; + + public OCSPResponse( + OCSPResponseStatus responseStatus, + ResponseBytes responseBytes) + { + this.responseStatus = responseStatus; + this.responseBytes = responseBytes; + } + + public OCSPResponse( + ASN1Sequence seq) + { + responseStatus = new OCSPResponseStatus( + DEREnumerated.getInstance(seq.getObjectAt(0))); + + if (seq.size() == 2) + { + responseBytes = ResponseBytes.getInstance( + (ASN1TaggedObject)seq.getObjectAt(1), true); + } + } + + public static OCSPResponse getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static OCSPResponse getInstance( + Object obj) + { + if (obj == null || obj instanceof OCSPResponse) + { + return (OCSPResponse)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new OCSPResponse((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public OCSPResponseStatus getResponseStatus() + { + return responseStatus; + } + + public ResponseBytes getResponseBytes() + { + return responseBytes; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * OCSPResponse ::= SEQUENCE {
+     *     responseStatus         OCSPResponseStatus,
+     *     responseBytes          [0] EXPLICIT ResponseBytes OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(responseStatus); + + if (responseBytes != null) + { + v.add(new DERTaggedObject(true, 0, responseBytes)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPResponseStatus.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPResponseStatus.java new file mode 100644 index 000000000..6eb020343 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/OCSPResponseStatus.java @@ -0,0 +1,40 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.DEREnumerated; + +public class OCSPResponseStatus + extends DEREnumerated +{ + public static final int SUCCESSFUL = 0; + public static final int MALFORMED_REQUEST = 1; + public static final int INTERNAL_ERROR = 2; + public static final int TRY_LATER = 3; + public static final int SIG_REQUIRED = 5; + public static final int UNAUTHORIZED = 6; + + /** + * The OCSPResponseStatus enumeration. + *
+     * OCSPResponseStatus ::= ENUMERATED {
+     *     successful            (0),  --Response has valid confirmations
+     *     malformedRequest      (1),  --Illegal confirmation request
+     *     internalError         (2),  --Internal error in issuer
+     *     tryLater              (3),  --Try again later
+     *                                 --(4) is not used
+     *     sigRequired           (5),  --Must sign the request
+     *     unauthorized          (6)   --Request unauthorized
+     * }
+     * 
+ */ + public OCSPResponseStatus( + int value) + { + super(value); + } + + public OCSPResponseStatus( + DEREnumerated value) + { + super(value.getValue().intValue()); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/Request.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/Request.java new file mode 100644 index 000000000..4d980af8e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/Request.java @@ -0,0 +1,91 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Extensions; + +public class Request + extends ASN1Encodable +{ + CertID reqCert; + X509Extensions singleRequestExtensions; + + public Request( + CertID reqCert, + X509Extensions singleRequestExtensions) + { + this.reqCert = reqCert; + this.singleRequestExtensions = singleRequestExtensions; + } + + public Request( + ASN1Sequence seq) + { + reqCert = CertID.getInstance(seq.getObjectAt(0)); + + if (seq.size() == 2) + { + singleRequestExtensions = X509Extensions.getInstance( + (ASN1TaggedObject)seq.getObjectAt(1), true); + } + } + + public static Request getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static Request getInstance( + Object obj) + { + if (obj == null || obj instanceof Request) + { + return (Request)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new Request((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public CertID getReqCert() + { + return reqCert; + } + + public X509Extensions getSingleRequestExtensions() + { + return singleRequestExtensions; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * Request         ::=     SEQUENCE {
+     *     reqCert                     CertID,
+     *     singleRequestExtensions     [0] EXPLICIT Extensions OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(reqCert); + + if (singleRequestExtensions != null) + { + v.add(new DERTaggedObject(true, 0, singleRequestExtensions)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/ResponderID.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/ResponderID.java new file mode 100644 index 000000000..31ccd31b6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/ResponderID.java @@ -0,0 +1,83 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Name; + +public class ResponderID + extends ASN1Encodable + implements ASN1Choice +{ + private DEREncodable value; + + public ResponderID( + ASN1OctetString value) + { + this.value = value; + } + + public ResponderID( + X509Name value) + { + this.value = value; + } + + public static ResponderID getInstance( + Object obj) + { + if (obj == null || obj instanceof ResponderID) + { + return (ResponderID)obj; + } + else if (obj instanceof DEROctetString) + { + return new ResponderID((DEROctetString)obj); + } + else if (obj instanceof ASN1TaggedObject) + { + ASN1TaggedObject o = (ASN1TaggedObject)obj; + + if (o.getTagNo() == 1) + { + return new ResponderID(X509Name.getInstance(o, true)); + } + else + { + return new ResponderID(ASN1OctetString.getInstance(o, true)); + } + } + + return new ResponderID(X509Name.getInstance(obj)); + } + + public static ResponderID getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); // must be explicitly tagged + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * ResponderID ::= CHOICE {
+     *      byName          [1] Name,
+     *      byKey           [2] KeyHash }
+     * 
+ */ + public DERObject toASN1Object() + { + if (value instanceof ASN1OctetString) + { + return new DERTaggedObject(true, 2, value); + } + + return new DERTaggedObject(true, 1, value); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/ResponseBytes.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/ResponseBytes.java new file mode 100644 index 000000000..31991f7fb --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/ResponseBytes.java @@ -0,0 +1,82 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class ResponseBytes + extends ASN1Encodable +{ + DERObjectIdentifier responseType; + ASN1OctetString response; + + public ResponseBytes( + DERObjectIdentifier responseType, + ASN1OctetString response) + { + this.responseType = responseType; + this.response = response; + } + + public ResponseBytes( + ASN1Sequence seq) + { + responseType = (DERObjectIdentifier)seq.getObjectAt(0); + response = (ASN1OctetString)seq.getObjectAt(1); + } + + public static ResponseBytes getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static ResponseBytes getInstance( + Object obj) + { + if (obj == null || obj instanceof ResponseBytes) + { + return (ResponseBytes)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new ResponseBytes((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public DERObjectIdentifier getResponseType() + { + return responseType; + } + + public ASN1OctetString getResponse() + { + return response; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * ResponseBytes ::=       SEQUENCE {
+     *     responseType   OBJECT IDENTIFIER,
+     *     response       OCTET STRING }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(responseType); + v.add(response); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/ResponseData.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/ResponseData.java new file mode 100644 index 000000000..19191e3b7 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/ResponseData.java @@ -0,0 +1,164 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Extensions; + +public class ResponseData + extends ASN1Encodable +{ + private static final DERInteger V1 = new DERInteger(0); + + private boolean versionPresent; + + private DERInteger version; + private ResponderID responderID; + private DERGeneralizedTime producedAt; + private ASN1Sequence responses; + private X509Extensions responseExtensions; + + public ResponseData( + DERInteger version, + ResponderID responderID, + DERGeneralizedTime producedAt, + ASN1Sequence responses, + X509Extensions responseExtensions) + { + this.version = version; + this.responderID = responderID; + this.producedAt = producedAt; + this.responses = responses; + this.responseExtensions = responseExtensions; + } + + public ResponseData( + ResponderID responderID, + DERGeneralizedTime producedAt, + ASN1Sequence responses, + X509Extensions responseExtensions) + { + this(V1, responderID, producedAt, responses, responseExtensions); + } + + public ResponseData( + ASN1Sequence seq) + { + int index = 0; + + if (seq.getObjectAt(0) instanceof ASN1TaggedObject) + { + ASN1TaggedObject o = (ASN1TaggedObject)seq.getObjectAt(0); + + if (o.getTagNo() == 0) + { + this.versionPresent = true; + this.version = DERInteger.getInstance( + (ASN1TaggedObject)seq.getObjectAt(0), true); + index++; + } + else + { + this.version = V1; + } + } + else + { + this.version = V1; + } + + this.responderID = ResponderID.getInstance(seq.getObjectAt(index++)); + this.producedAt = (DERGeneralizedTime)seq.getObjectAt(index++); + this.responses = (ASN1Sequence)seq.getObjectAt(index++); + + if (seq.size() > index) + { + this.responseExtensions = X509Extensions.getInstance( + (ASN1TaggedObject)seq.getObjectAt(index), true); + } + } + + public static ResponseData getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static ResponseData getInstance( + Object obj) + { + if (obj == null || obj instanceof ResponseData) + { + return (ResponseData)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new ResponseData((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public DERInteger getVersion() + { + return version; + } + + public ResponderID getResponderID() + { + return responderID; + } + + public DERGeneralizedTime getProducedAt() + { + return producedAt; + } + + public ASN1Sequence getResponses() + { + return responses; + } + + public X509Extensions getResponseExtensions() + { + return responseExtensions; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * ResponseData ::= SEQUENCE {
+     *     version              [0] EXPLICIT Version DEFAULT v1,
+     *     responderID              ResponderID,
+     *     producedAt               GeneralizedTime,
+     *     responses                SEQUENCE OF SingleResponse,
+     *     responseExtensions   [1] EXPLICIT Extensions OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (versionPresent || !version.equals(V1)) + { + v.add(new DERTaggedObject(true, 0, version)); + } + + v.add(responderID); + v.add(producedAt); + v.add(responses); + if (responseExtensions != null) + { + v.add(new DERTaggedObject(true, 1, responseExtensions)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/RevokedInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/RevokedInfo.java new file mode 100644 index 000000000..f9197ddeb --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/RevokedInfo.java @@ -0,0 +1,92 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREnumerated; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.CRLReason; + +public class RevokedInfo + extends ASN1Encodable +{ + private DERGeneralizedTime revocationTime; + private CRLReason revocationReason; + + public RevokedInfo( + DERGeneralizedTime revocationTime, + CRLReason revocationReason) + { + this.revocationTime = revocationTime; + this.revocationReason = revocationReason; + } + + public RevokedInfo( + ASN1Sequence seq) + { + this.revocationTime = (DERGeneralizedTime)seq.getObjectAt(0); + + if (seq.size() > 1) + { + this.revocationReason = new CRLReason(DEREnumerated.getInstance( + (ASN1TaggedObject)seq.getObjectAt(1), true)); + } + } + + public static RevokedInfo getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static RevokedInfo getInstance( + Object obj) + { + if (obj == null || obj instanceof RevokedInfo) + { + return (RevokedInfo)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new RevokedInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public DERGeneralizedTime getRevocationTime() + { + return revocationTime; + } + + public CRLReason getRevocationReason() + { + return revocationReason; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * RevokedInfo ::= SEQUENCE {
+     *      revocationTime              GeneralizedTime,
+     *      revocationReason    [0]     EXPLICIT CRLReason OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(revocationTime); + if (revocationReason != null) + { + v.add(new DERTaggedObject(true, 0, revocationReason)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/ServiceLocator.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/ServiceLocator.java new file mode 100644 index 000000000..3fedae900 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/ServiceLocator.java @@ -0,0 +1,36 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Name; + +public class ServiceLocator + extends ASN1Encodable +{ + X509Name issuer; + DERObject locator; + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * ServiceLocator ::= SEQUENCE {
+     *     issuer    Name,
+     *     locator   AuthorityInfoAccessSyntax OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(issuer); + + if (locator != null) + { + v.add(locator); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/Signature.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/Signature.java new file mode 100644 index 000000000..e8eaef46e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/Signature.java @@ -0,0 +1,111 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class Signature + extends ASN1Encodable +{ + AlgorithmIdentifier signatureAlgorithm; + DERBitString signature; + ASN1Sequence certs; + + public Signature( + AlgorithmIdentifier signatureAlgorithm, + DERBitString signature) + { + this.signatureAlgorithm = signatureAlgorithm; + this.signature = signature; + } + + public Signature( + AlgorithmIdentifier signatureAlgorithm, + DERBitString signature, + ASN1Sequence certs) + { + this.signatureAlgorithm = signatureAlgorithm; + this.signature = signature; + this.certs = certs; + } + + public Signature( + ASN1Sequence seq) + { + signatureAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(0)); + signature = (DERBitString)seq.getObjectAt(1); + + if (seq.size() == 3) + { + certs = ASN1Sequence.getInstance( + (ASN1TaggedObject)seq.getObjectAt(2), true); + } + } + + public static Signature getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static Signature getInstance( + Object obj) + { + if (obj == null || obj instanceof Signature) + { + return (Signature)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new Signature((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public AlgorithmIdentifier getSignatureAlgorithm() + { + return signatureAlgorithm; + } + + public DERBitString getSignature() + { + return signature; + } + + public ASN1Sequence getCerts() + { + return certs; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * Signature       ::=     SEQUENCE {
+     *     signatureAlgorithm      AlgorithmIdentifier,
+     *     signature               BIT STRING,
+     *     certs               [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL}
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(signatureAlgorithm); + v.add(signature); + + if (certs != null) + { + v.add(new DERTaggedObject(true, 0, certs)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/SingleResponse.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/SingleResponse.java new file mode 100644 index 000000000..41e950e61 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/SingleResponse.java @@ -0,0 +1,143 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Extensions; + +public class SingleResponse + extends ASN1Encodable +{ + private CertID certID; + private CertStatus certStatus; + private DERGeneralizedTime thisUpdate; + private DERGeneralizedTime nextUpdate; + private X509Extensions singleExtensions; + + public SingleResponse( + CertID certID, + CertStatus certStatus, + DERGeneralizedTime thisUpdate, + DERGeneralizedTime nextUpdate, + X509Extensions singleExtensions) + { + this.certID = certID; + this.certStatus = certStatus; + this.thisUpdate = thisUpdate; + this.nextUpdate = nextUpdate; + this.singleExtensions = singleExtensions; + } + + public SingleResponse( + ASN1Sequence seq) + { + this.certID = CertID.getInstance(seq.getObjectAt(0)); + this.certStatus = CertStatus.getInstance(seq.getObjectAt(1)); + this.thisUpdate = (DERGeneralizedTime)seq.getObjectAt(2); + + if (seq.size() > 4) + { + this.nextUpdate = DERGeneralizedTime.getInstance( + (ASN1TaggedObject)seq.getObjectAt(3), true); + this.singleExtensions = X509Extensions.getInstance( + (ASN1TaggedObject)seq.getObjectAt(4), true); + } + else if (seq.size() > 3) + { + ASN1TaggedObject o = (ASN1TaggedObject)seq.getObjectAt(3); + + if (o.getTagNo() == 0) + { + this.nextUpdate = DERGeneralizedTime.getInstance(o, true); + } + else + { + this.singleExtensions = X509Extensions.getInstance(o, true); + } + } + } + + public static SingleResponse getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static SingleResponse getInstance( + Object obj) + { + if (obj == null || obj instanceof SingleResponse) + { + return (SingleResponse)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new SingleResponse((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public CertID getCertID() + { + return certID; + } + + public CertStatus getCertStatus() + { + return certStatus; + } + + public DERGeneralizedTime getThisUpdate() + { + return thisUpdate; + } + + public DERGeneralizedTime getNextUpdate() + { + return nextUpdate; + } + + public X509Extensions getSingleExtensions() + { + return singleExtensions; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  SingleResponse ::= SEQUENCE {
+     *          certID                       CertID,
+     *          certStatus                   CertStatus,
+     *          thisUpdate                   GeneralizedTime,
+     *          nextUpdate         [0]       EXPLICIT GeneralizedTime OPTIONAL,
+     *          singleExtensions   [1]       EXPLICIT Extensions OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(certID); + v.add(certStatus); + v.add(thisUpdate); + + if (nextUpdate != null) + { + v.add(new DERTaggedObject(true, 0, nextUpdate)); + } + + if (singleExtensions != null) + { + v.add(new DERTaggedObject(true, 1, singleExtensions)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/ocsp/TBSRequest.java b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/TBSRequest.java new file mode 100644 index 000000000..b57ad5ee3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/ocsp/TBSRequest.java @@ -0,0 +1,154 @@ +package com.google.bitcoin.bouncycastle.asn1.ocsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.GeneralName; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Extensions; + +public class TBSRequest + extends ASN1Encodable +{ + private static final DERInteger V1 = new DERInteger(0); + + DERInteger version; + GeneralName requestorName; + ASN1Sequence requestList; + X509Extensions requestExtensions; + + boolean versionSet; + + public TBSRequest( + GeneralName requestorName, + ASN1Sequence requestList, + X509Extensions requestExtensions) + { + this.version = V1; + this.requestorName = requestorName; + this.requestList = requestList; + this.requestExtensions = requestExtensions; + } + + public TBSRequest( + ASN1Sequence seq) + { + int index = 0; + + if (seq.getObjectAt(0) instanceof ASN1TaggedObject) + { + ASN1TaggedObject o = (ASN1TaggedObject)seq.getObjectAt(0); + + if (o.getTagNo() == 0) + { + versionSet = true; + version = DERInteger.getInstance((ASN1TaggedObject)seq.getObjectAt(0), true); + index++; + } + else + { + version = V1; + } + } + else + { + version = V1; + } + + if (seq.getObjectAt(index) instanceof ASN1TaggedObject) + { + requestorName = GeneralName.getInstance((ASN1TaggedObject)seq.getObjectAt(index++), true); + } + + requestList = (ASN1Sequence)seq.getObjectAt(index++); + + if (seq.size() == (index + 1)) + { + requestExtensions = X509Extensions.getInstance((ASN1TaggedObject)seq.getObjectAt(index), true); + } + } + + public static TBSRequest getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static TBSRequest getInstance( + Object obj) + { + if (obj == null || obj instanceof TBSRequest) + { + return (TBSRequest)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new TBSRequest((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public DERInteger getVersion() + { + return version; + } + + public GeneralName getRequestorName() + { + return requestorName; + } + + public ASN1Sequence getRequestList() + { + return requestList; + } + + public X509Extensions getRequestExtensions() + { + return requestExtensions; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * TBSRequest      ::=     SEQUENCE {
+     *     version             [0]     EXPLICIT Version DEFAULT v1,
+     *     requestorName       [1]     EXPLICIT GeneralName OPTIONAL,
+     *     requestList                 SEQUENCE OF Request,
+     *     requestExtensions   [2]     EXPLICIT Extensions OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + // + // if default don't include - unless explicitly provided. Not strictly correct + // but required for some requests + // + if (!version.equals(V1) || versionSet) + { + v.add(new DERTaggedObject(true, 0, version)); + } + + if (requestorName != null) + { + v.add(new DERTaggedObject(true, 1, requestorName)); + } + + v.add(requestList); + + if (requestExtensions != null) + { + v.add(new DERTaggedObject(true, 2, requestExtensions)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/oiw/ElGamalParameter.java b/src/com/google/bitcoin/bouncycastle/asn1/oiw/ElGamalParameter.java new file mode 100644 index 000000000..240ebabc5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/oiw/ElGamalParameter.java @@ -0,0 +1,49 @@ +package com.google.bitcoin.bouncycastle.asn1.oiw; + +import java.math.*; +import java.util.*; + +import com.google.bitcoin.bouncycastle.asn1.*; + +public class ElGamalParameter + extends ASN1Encodable +{ + DERInteger p, g; + + public ElGamalParameter( + BigInteger p, + BigInteger g) + { + this.p = new DERInteger(p); + this.g = new DERInteger(g); + } + + public ElGamalParameter( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + p = (DERInteger)e.nextElement(); + g = (DERInteger)e.nextElement(); + } + + public BigInteger getP() + { + return p.getPositiveValue(); + } + + public BigInteger getG() + { + return g.getPositiveValue(); + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(p); + v.add(g); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/oiw/OIWObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/oiw/OIWObjectIdentifiers.java new file mode 100644 index 000000000..79b964d57 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/oiw/OIWObjectIdentifiers.java @@ -0,0 +1,31 @@ +package com.google.bitcoin.bouncycastle.asn1.oiw; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface OIWObjectIdentifiers +{ + // id-SHA1 OBJECT IDENTIFIER ::= + // {iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) 26 } // + static final DERObjectIdentifier md4WithRSA = new DERObjectIdentifier("1.3.14.3.2.2"); + static final DERObjectIdentifier md5WithRSA = new DERObjectIdentifier("1.3.14.3.2.3"); + static final DERObjectIdentifier md4WithRSAEncryption = new DERObjectIdentifier("1.3.14.3.2.4"); + + static final DERObjectIdentifier desECB = new DERObjectIdentifier("1.3.14.3.2.6"); + static final DERObjectIdentifier desCBC = new DERObjectIdentifier("1.3.14.3.2.7"); + static final DERObjectIdentifier desOFB = new DERObjectIdentifier("1.3.14.3.2.8"); + static final DERObjectIdentifier desCFB = new DERObjectIdentifier("1.3.14.3.2.9"); + + static final DERObjectIdentifier desEDE = new DERObjectIdentifier("1.3.14.3.2.17"); + + static final DERObjectIdentifier idSHA1 = new DERObjectIdentifier("1.3.14.3.2.26"); + + static final DERObjectIdentifier dsaWithSHA1 = new DERObjectIdentifier("1.3.14.3.2.27"); + + static final DERObjectIdentifier sha1WithRSA = new DERObjectIdentifier("1.3.14.3.2.29"); + + // ElGamal Algorithm OBJECT IDENTIFIER ::= + // {iso(1) identified-organization(3) oiw(14) dirservsig(7) algorithm(2) encryption(1) 1 } + // + static final DERObjectIdentifier elGamalAlgorithm = new DERObjectIdentifier("1.3.14.7.2.1.1"); + +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/Attribute.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/Attribute.java new file mode 100644 index 000000000..d7a7e9a7a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/Attribute.java @@ -0,0 +1,82 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class Attribute + extends ASN1Encodable +{ + private DERObjectIdentifier attrType; + private ASN1Set attrValues; + + /** + * return an Attribute object from the given object. + * + * @param o the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static Attribute getInstance( + Object o) + { + if (o == null || o instanceof Attribute) + { + return (Attribute)o; + } + + if (o instanceof ASN1Sequence) + { + return new Attribute((ASN1Sequence)o); + } + + throw new IllegalArgumentException("unknown object in factory: " + o.getClass().getName()); + } + + public Attribute( + ASN1Sequence seq) + { + attrType = (DERObjectIdentifier)seq.getObjectAt(0); + attrValues = (ASN1Set)seq.getObjectAt(1); + } + + public Attribute( + DERObjectIdentifier attrType, + ASN1Set attrValues) + { + this.attrType = attrType; + this.attrValues = attrValues; + } + + public DERObjectIdentifier getAttrType() + { + return attrType; + } + + public ASN1Set getAttrValues() + { + return attrValues; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * Attribute ::= SEQUENCE {
+     *     attrType OBJECT IDENTIFIER,
+     *     attrValues SET OF AttributeValue
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(attrType); + v.add(attrValues); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/AuthenticatedSafe.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/AuthenticatedSafe.java new file mode 100644 index 000000000..57de56e28 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/AuthenticatedSafe.java @@ -0,0 +1,47 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.BERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +public class AuthenticatedSafe + extends ASN1Encodable +{ + ContentInfo[] info; + + public AuthenticatedSafe( + ASN1Sequence seq) + { + info = new ContentInfo[seq.size()]; + + for (int i = 0; i != info.length; i++) + { + info[i] = ContentInfo.getInstance(seq.getObjectAt(i)); + } + } + + public AuthenticatedSafe( + ContentInfo[] info) + { + this.info = info; + } + + public ContentInfo[] getContentInfo() + { + return info; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + for (int i = 0; i != info.length; i++) + { + v.add(info[i]); + } + + return new BERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/CertBag.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/CertBag.java new file mode 100644 index 000000000..ab1dc632b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/CertBag.java @@ -0,0 +1,53 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class CertBag + extends ASN1Encodable +{ + ASN1Sequence seq; + DERObjectIdentifier certId; + DERObject certValue; + + public CertBag( + ASN1Sequence seq) + { + this.seq = seq; + this.certId = (DERObjectIdentifier)seq.getObjectAt(0); + this.certValue = ((DERTaggedObject)seq.getObjectAt(1)).getObject(); + } + + public CertBag( + DERObjectIdentifier certId, + DERObject certValue) + { + this.certId = certId; + this.certValue = certValue; + } + + public DERObjectIdentifier getCertId() + { + return certId; + } + + public DERObject getCertValue() + { + return certValue; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(certId); + v.add(new DERTaggedObject(0, certValue)); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/CertificationRequest.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/CertificationRequest.java new file mode 100644 index 000000000..d5f0a59ba --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/CertificationRequest.java @@ -0,0 +1,91 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +/** + * PKCS10 Certification request object. + *
+ * CertificationRequest ::= SEQUENCE {
+ *   certificationRequestInfo  CertificationRequestInfo,
+ *   signatureAlgorithm        AlgorithmIdentifier{{ SignatureAlgorithms }},
+ *   signature                 BIT STRING
+ * }
+ * 
+ */ +public class CertificationRequest + extends ASN1Encodable +{ + protected CertificationRequestInfo reqInfo = null; + protected AlgorithmIdentifier sigAlgId = null; + protected DERBitString sigBits = null; + + public static CertificationRequest getInstance(Object o) + { + if (o instanceof CertificationRequest) + { + return (CertificationRequest)o; + } + + if (o instanceof ASN1Sequence) + { + return new CertificationRequest((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Invalid object: " + o.getClass().getName()); + } + + protected CertificationRequest() + { + } + + public CertificationRequest( + CertificationRequestInfo requestInfo, + AlgorithmIdentifier algorithm, + DERBitString signature) + { + this.reqInfo = requestInfo; + this.sigAlgId = algorithm; + this.sigBits = signature; + } + + public CertificationRequest( + ASN1Sequence seq) + { + reqInfo = CertificationRequestInfo.getInstance(seq.getObjectAt(0)); + sigAlgId = AlgorithmIdentifier.getInstance(seq.getObjectAt(1)); + sigBits = (DERBitString)seq.getObjectAt(2); + } + + public CertificationRequestInfo getCertificationRequestInfo() + { + return reqInfo; + } + + public AlgorithmIdentifier getSignatureAlgorithm() + { + return sigAlgId; + } + + public DERBitString getSignature() + { + return sigBits; + } + + public DERObject toASN1Object() + { + // Construct the CertificateRequest + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(reqInfo); + v.add(sigAlgId); + v.add(sigBits); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/CertificationRequestInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/CertificationRequestInfo.java new file mode 100644 index 000000000..62ef892b1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/CertificationRequestInfo.java @@ -0,0 +1,129 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Name; + +/** + * PKCS10 CertificationRequestInfo object. + *
+ *  CertificationRequestInfo ::= SEQUENCE {
+ *   version             INTEGER { v1(0) } (v1,...),
+ *   subject             Name,
+ *   subjectPKInfo   SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
+ *   attributes          [0] Attributes{{ CRIAttributes }}
+ *  }
+ *
+ *  Attributes { ATTRIBUTE:IOSet } ::= SET OF Attribute{{ IOSet }}
+ *
+ *  Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE {
+ *    type    ATTRIBUTE.&id({IOSet}),
+ *    values  SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{\@type})
+ *  }
+ * 
+ */ +public class CertificationRequestInfo + extends ASN1Encodable +{ + DERInteger version = new DERInteger(0); + X509Name subject; + SubjectPublicKeyInfo subjectPKInfo; + ASN1Set attributes = null; + + public static CertificationRequestInfo getInstance( + Object obj) + { + if (obj instanceof CertificationRequestInfo) + { + return (CertificationRequestInfo)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new CertificationRequestInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public CertificationRequestInfo( + X509Name subject, + SubjectPublicKeyInfo pkInfo, + ASN1Set attributes) + { + this.subject = subject; + this.subjectPKInfo = pkInfo; + this.attributes = attributes; + + if ((subject == null) || (version == null) || (subjectPKInfo == null)) + { + throw new IllegalArgumentException("Not all mandatory fields set in CertificationRequestInfo generator."); + } + } + + public CertificationRequestInfo( + ASN1Sequence seq) + { + version = (DERInteger)seq.getObjectAt(0); + + subject = X509Name.getInstance(seq.getObjectAt(1)); + subjectPKInfo = SubjectPublicKeyInfo.getInstance(seq.getObjectAt(2)); + + // + // some CertificationRequestInfo objects seem to treat this field + // as optional. + // + if (seq.size() > 3) + { + DERTaggedObject tagobj = (DERTaggedObject)seq.getObjectAt(3); + attributes = ASN1Set.getInstance(tagobj, false); + } + + if ((subject == null) || (version == null) || (subjectPKInfo == null)) + { + throw new IllegalArgumentException("Not all mandatory fields set in CertificationRequestInfo generator."); + } + } + + public DERInteger getVersion() + { + return version; + } + + public X509Name getSubject() + { + return subject; + } + + public SubjectPublicKeyInfo getSubjectPublicKeyInfo() + { + return subjectPKInfo; + } + + public ASN1Set getAttributes() + { + return attributes; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(subject); + v.add(subjectPKInfo); + + if (attributes != null) + { + v.add(new DERTaggedObject(false, 0, attributes)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/ContentInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/ContentInfo.java new file mode 100644 index 000000000..c1a8433e1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/ContentInfo.java @@ -0,0 +1,90 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.BERSequence; +import com.google.bitcoin.bouncycastle.asn1.BERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class ContentInfo + extends ASN1Encodable + implements PKCSObjectIdentifiers +{ + private DERObjectIdentifier contentType; + private DEREncodable content; + + public static ContentInfo getInstance( + Object obj) + { + if (obj instanceof ContentInfo) + { + return (ContentInfo)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new ContentInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public ContentInfo( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + contentType = (DERObjectIdentifier)e.nextElement(); + + if (e.hasMoreElements()) + { + content = ((DERTaggedObject)e.nextElement()).getObject(); + } + } + + public ContentInfo( + DERObjectIdentifier contentType, + DEREncodable content) + { + this.contentType = contentType; + this.content = content; + } + + public DERObjectIdentifier getContentType() + { + return contentType; + } + + public DEREncodable getContent() + { + return content; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * ContentInfo ::= SEQUENCE {
+     *          contentType ContentType,
+     *          content
+     *          [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(contentType); + + if (content != null) + { + v.add(new BERTaggedObject(0, content)); + } + + return new BERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/DHParameter.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/DHParameter.java new file mode 100644 index 000000000..3edec4a02 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/DHParameter.java @@ -0,0 +1,88 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import java.math.BigInteger; +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class DHParameter + extends ASN1Encodable +{ + DERInteger p, g, l; + + public DHParameter( + BigInteger p, + BigInteger g, + int l) + { + this.p = new DERInteger(p); + this.g = new DERInteger(g); + + if (l != 0) + { + this.l = new DERInteger(l); + } + else + { + this.l = null; + } + } + + public DHParameter( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + p = (DERInteger)e.nextElement(); + g = (DERInteger)e.nextElement(); + + if (e.hasMoreElements()) + { + l = (DERInteger)e.nextElement(); + } + else + { + l = null; + } + } + + public BigInteger getP() + { + return p.getPositiveValue(); + } + + public BigInteger getG() + { + return g.getPositiveValue(); + } + + public BigInteger getL() + { + if (l == null) + { + return null; + } + + return l.getPositiveValue(); + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(p); + v.add(g); + + if (this.getL() != null) + { + v.add(l); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/EncryptedData.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/EncryptedData.java new file mode 100644 index 000000000..d966995d5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/EncryptedData.java @@ -0,0 +1,104 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import com.google.bitcoin.bouncycastle.asn1.*; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +/** + * The EncryptedData object. + *
+ *      EncryptedData ::= SEQUENCE {
+ *           version Version,
+ *           encryptedContentInfo EncryptedContentInfo
+ *      }
+ *
+ *
+ *      EncryptedContentInfo ::= SEQUENCE {
+ *          contentType ContentType,
+ *          contentEncryptionAlgorithm  ContentEncryptionAlgorithmIdentifier,
+ *          encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL
+ *    }
+ *
+ *    EncryptedContent ::= OCTET STRING
+ * 
+ */ +public class EncryptedData + extends ASN1Encodable +{ + ASN1Sequence data; + DERObjectIdentifier bagId; + DERObject bagValue; + + public static EncryptedData getInstance( + Object obj) + { + if (obj instanceof EncryptedData) + { + return (EncryptedData)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new EncryptedData((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public EncryptedData( + ASN1Sequence seq) + { + int version = ((DERInteger)seq.getObjectAt(0)).getValue().intValue(); + + if (version != 0) + { + throw new IllegalArgumentException("sequence not version 0"); + } + + this.data = (ASN1Sequence)seq.getObjectAt(1); + } + + public EncryptedData( + DERObjectIdentifier contentType, + AlgorithmIdentifier encryptionAlgorithm, + DEREncodable content) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(contentType); + v.add(encryptionAlgorithm.getDERObject()); + v.add(new BERTaggedObject(false, 0, content)); + + data = new BERSequence(v); + } + + public DERObjectIdentifier getContentType() + { + return (DERObjectIdentifier)data.getObjectAt(0); + } + + public AlgorithmIdentifier getEncryptionAlgorithm() + { + return AlgorithmIdentifier.getInstance(data.getObjectAt(1)); + } + + public ASN1OctetString getContent() + { + if (data.size() == 3) + { + DERTaggedObject o = (DERTaggedObject)data.getObjectAt(2); + + return ASN1OctetString.getInstance(o.getObject()); + } + + return null; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new DERInteger(0)); + v.add(data); + + return new BERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/EncryptedPrivateKeyInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/EncryptedPrivateKeyInfo.java new file mode 100644 index 000000000..e0edbf6b4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/EncryptedPrivateKeyInfo.java @@ -0,0 +1,86 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class EncryptedPrivateKeyInfo + extends ASN1Encodable +{ + private AlgorithmIdentifier algId; + private ASN1OctetString data; + + public EncryptedPrivateKeyInfo( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + algId = AlgorithmIdentifier.getInstance(e.nextElement()); + data = (ASN1OctetString)e.nextElement(); + } + + public EncryptedPrivateKeyInfo( + AlgorithmIdentifier algId, + byte[] encoding) + { + this.algId = algId; + this.data = new DEROctetString(encoding); + } + + public static EncryptedPrivateKeyInfo getInstance( + Object obj) + { + if (obj instanceof EncryptedData) + { + return (EncryptedPrivateKeyInfo)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new EncryptedPrivateKeyInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public AlgorithmIdentifier getEncryptionAlgorithm() + { + return algId; + } + + public byte[] getEncryptedData() + { + return data.getOctets(); + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * EncryptedPrivateKeyInfo ::= SEQUENCE {
+     *      encryptionAlgorithm AlgorithmIdentifier {{KeyEncryptionAlgorithms}},
+     *      encryptedData EncryptedData
+     * }
+     *
+     * EncryptedData ::= OCTET STRING
+     *
+     * KeyEncryptionAlgorithms ALGORITHM-IDENTIFIER ::= {
+     *          ... -- For local profiles
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(algId); + v.add(data); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/EncryptionScheme.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/EncryptionScheme.java new file mode 100644 index 000000000..bfb17362c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/EncryptionScheme.java @@ -0,0 +1,38 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class EncryptionScheme + extends AlgorithmIdentifier +{ + DERObject objectId; + DERObject obj; + + EncryptionScheme( + ASN1Sequence seq) + { + super(seq); + + objectId = (DERObject)seq.getObjectAt(0); + obj = (DERObject)seq.getObjectAt(1); + } + + public DERObject getObject() + { + return obj; + } + + public DERObject getDERObject() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(objectId); + v.add(obj); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/IssuerAndSerialNumber.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/IssuerAndSerialNumber.java new file mode 100644 index 000000000..4c99778e4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/IssuerAndSerialNumber.java @@ -0,0 +1,76 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Name; + +public class IssuerAndSerialNumber + extends ASN1Encodable +{ + X509Name name; + DERInteger certSerialNumber; + + public static IssuerAndSerialNumber getInstance( + Object obj) + { + if (obj instanceof IssuerAndSerialNumber) + { + return (IssuerAndSerialNumber)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new IssuerAndSerialNumber((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public IssuerAndSerialNumber( + ASN1Sequence seq) + { + this.name = X509Name.getInstance(seq.getObjectAt(0)); + this.certSerialNumber = (DERInteger)seq.getObjectAt(1); + } + + public IssuerAndSerialNumber( + X509Name name, + BigInteger certSerialNumber) + { + this.name = name; + this.certSerialNumber = new DERInteger(certSerialNumber); + } + + public IssuerAndSerialNumber( + X509Name name, + DERInteger certSerialNumber) + { + this.name = name; + this.certSerialNumber = certSerialNumber; + } + + public X509Name getName() + { + return name; + } + + public DERInteger getCertificateSerialNumber() + { + return certSerialNumber; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(name); + v.add(certSerialNumber); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/KeyDerivationFunc.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/KeyDerivationFunc.java new file mode 100644 index 000000000..bd6b8114f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/KeyDerivationFunc.java @@ -0,0 +1,23 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class KeyDerivationFunc + extends AlgorithmIdentifier +{ + KeyDerivationFunc( + ASN1Sequence seq) + { + super(seq); + } + + KeyDerivationFunc( + DERObjectIdentifier id, + ASN1Encodable params) + { + super(id, params); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/MacData.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/MacData.java new file mode 100644 index 000000000..d0e386f43 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/MacData.java @@ -0,0 +1,106 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.DigestInfo; + +public class MacData + extends ASN1Encodable +{ + private static final BigInteger ONE = BigInteger.valueOf(1); + + DigestInfo digInfo; + byte[] salt; + BigInteger iterationCount; + + public static MacData getInstance( + Object obj) + { + if (obj instanceof MacData) + { + return (MacData)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new MacData((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public MacData( + ASN1Sequence seq) + { + this.digInfo = DigestInfo.getInstance(seq.getObjectAt(0)); + + this.salt = ((ASN1OctetString)seq.getObjectAt(1)).getOctets(); + + if (seq.size() == 3) + { + this.iterationCount = ((DERInteger)seq.getObjectAt(2)).getValue(); + } + else + { + this.iterationCount = ONE; + } + } + + public MacData( + DigestInfo digInfo, + byte[] salt, + int iterationCount) + { + this.digInfo = digInfo; + this.salt = salt; + this.iterationCount = BigInteger.valueOf(iterationCount); + } + + public DigestInfo getMac() + { + return digInfo; + } + + public byte[] getSalt() + { + return salt; + } + + public BigInteger getIterationCount() + { + return iterationCount; + } + + /** + *
+     * MacData ::= SEQUENCE {
+     *     mac      DigestInfo,
+     *     macSalt  OCTET STRING,
+     *     iterations INTEGER DEFAULT 1
+     *     -- Note: The default is for historic reasons and its use is deprecated. A
+     *     -- higher value, like 1024 is recommended.
+     * 
+ * @return the basic DERObject construction. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(digInfo); + v.add(new DEROctetString(salt)); + + if (!iterationCount.equals(ONE)) + { + v.add(new DERInteger(iterationCount)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PBES2Algorithms.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PBES2Algorithms.java new file mode 100644 index 000000000..642db1603 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PBES2Algorithms.java @@ -0,0 +1,77 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +/** + * @deprecated - use AlgorithmIdentifier and PBES2Parameters + */ +public class PBES2Algorithms + extends AlgorithmIdentifier implements PKCSObjectIdentifiers +{ + private DERObjectIdentifier objectId; + private KeyDerivationFunc func; + private EncryptionScheme scheme; + + public PBES2Algorithms( + ASN1Sequence obj) + { + super(obj); + + Enumeration e = obj.getObjects(); + + objectId = (DERObjectIdentifier)e.nextElement(); + + ASN1Sequence seq = (ASN1Sequence)e.nextElement(); + + e = seq.getObjects(); + + ASN1Sequence funcSeq = (ASN1Sequence)e.nextElement(); + + if (funcSeq.getObjectAt(0).equals(id_PBKDF2)) + { + func = new KeyDerivationFunc(id_PBKDF2, PBKDF2Params.getInstance(funcSeq.getObjectAt(1))); + } + else + { + func = new KeyDerivationFunc(funcSeq); + } + + scheme = new EncryptionScheme((ASN1Sequence)e.nextElement()); + } + + public DERObjectIdentifier getObjectId() + { + return objectId; + } + + public KeyDerivationFunc getKeyDerivationFunc() + { + return func; + } + + public EncryptionScheme getEncryptionScheme() + { + return scheme; + } + + public DERObject getDERObject() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + ASN1EncodableVector subV = new ASN1EncodableVector(); + + v.add(objectId); + + subV.add(func); + subV.add(scheme); + v.add(new DERSequence(subV)); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PBES2Parameters.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PBES2Parameters.java new file mode 100644 index 000000000..57e4deca9 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PBES2Parameters.java @@ -0,0 +1,55 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class PBES2Parameters + extends ASN1Encodable + implements PKCSObjectIdentifiers +{ + private KeyDerivationFunc func; + private EncryptionScheme scheme; + + public PBES2Parameters( + ASN1Sequence obj) + { + Enumeration e = obj.getObjects(); + ASN1Sequence funcSeq = (ASN1Sequence)e.nextElement(); + + if (funcSeq.getObjectAt(0).equals(id_PBKDF2)) + { + func = new KeyDerivationFunc(id_PBKDF2, PBKDF2Params.getInstance(funcSeq.getObjectAt(1))); + } + else + { + func = new KeyDerivationFunc(funcSeq); + } + + scheme = new EncryptionScheme((ASN1Sequence)e.nextElement()); + } + + public KeyDerivationFunc getKeyDerivationFunc() + { + return func; + } + + public EncryptionScheme getEncryptionScheme() + { + return scheme; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(func); + v.add(scheme); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PBKDF2Params.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PBKDF2Params.java new file mode 100644 index 000000000..b81c80b00 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PBKDF2Params.java @@ -0,0 +1,98 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import java.math.BigInteger; +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class PBKDF2Params + extends ASN1Encodable +{ + ASN1OctetString octStr; + DERInteger iterationCount; + DERInteger keyLength; + + public static PBKDF2Params getInstance( + Object obj) + { + if (obj instanceof PBKDF2Params) + { + return (PBKDF2Params)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new PBKDF2Params((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public PBKDF2Params( + byte[] salt, + int iterationCount) + { + this.octStr = new DEROctetString(salt); + this.iterationCount = new DERInteger(iterationCount); + } + + public PBKDF2Params( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + octStr = (ASN1OctetString)e.nextElement(); + iterationCount = (DERInteger)e.nextElement(); + + if (e.hasMoreElements()) + { + keyLength = (DERInteger)e.nextElement(); + } + else + { + keyLength = null; + } + } + + public byte[] getSalt() + { + return octStr.getOctets(); + } + + public BigInteger getIterationCount() + { + return iterationCount.getValue(); + } + + public BigInteger getKeyLength() + { + if (keyLength != null) + { + return keyLength.getValue(); + } + + return null; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(octStr); + v.add(iterationCount); + + if (keyLength != null) + { + v.add(keyLength); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PKCS12PBEParams.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PKCS12PBEParams.java new file mode 100644 index 000000000..5b1005553 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PKCS12PBEParams.java @@ -0,0 +1,69 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class PKCS12PBEParams + extends ASN1Encodable +{ + DERInteger iterations; + ASN1OctetString iv; + + public PKCS12PBEParams( + byte[] salt, + int iterations) + { + this.iv = new DEROctetString(salt); + this.iterations = new DERInteger(iterations); + } + + public PKCS12PBEParams( + ASN1Sequence seq) + { + iv = (ASN1OctetString)seq.getObjectAt(0); + iterations = (DERInteger)seq.getObjectAt(1); + } + + public static PKCS12PBEParams getInstance( + Object obj) + { + if (obj instanceof PKCS12PBEParams) + { + return (PKCS12PBEParams)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new PKCS12PBEParams((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public BigInteger getIterations() + { + return iterations.getValue(); + } + + public byte[] getIV() + { + return iv.getOctets(); + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(iv); + v.add(iterations); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PKCSObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PKCSObjectIdentifiers.java new file mode 100644 index 000000000..2948a70a7 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PKCSObjectIdentifiers.java @@ -0,0 +1,248 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface PKCSObjectIdentifiers +{ + // + // pkcs-1 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1 } + // + static final String pkcs_1 = "1.2.840.113549.1.1"; + static final DERObjectIdentifier rsaEncryption = new DERObjectIdentifier(pkcs_1 + ".1"); + static final DERObjectIdentifier md2WithRSAEncryption = new DERObjectIdentifier(pkcs_1 + ".2"); + static final DERObjectIdentifier md4WithRSAEncryption = new DERObjectIdentifier(pkcs_1 + ".3"); + static final DERObjectIdentifier md5WithRSAEncryption = new DERObjectIdentifier(pkcs_1 + ".4"); + static final DERObjectIdentifier sha1WithRSAEncryption = new DERObjectIdentifier(pkcs_1 + ".5"); + static final DERObjectIdentifier srsaOAEPEncryptionSET = new DERObjectIdentifier(pkcs_1 + ".6"); + static final DERObjectIdentifier id_RSAES_OAEP = new DERObjectIdentifier(pkcs_1 + ".7"); + static final DERObjectIdentifier id_mgf1 = new DERObjectIdentifier(pkcs_1 + ".8"); + static final DERObjectIdentifier id_pSpecified = new DERObjectIdentifier(pkcs_1 + ".9"); + static final DERObjectIdentifier id_RSASSA_PSS = new DERObjectIdentifier(pkcs_1 + ".10"); + static final DERObjectIdentifier sha256WithRSAEncryption = new DERObjectIdentifier(pkcs_1 + ".11"); + static final DERObjectIdentifier sha384WithRSAEncryption = new DERObjectIdentifier(pkcs_1 + ".12"); + static final DERObjectIdentifier sha512WithRSAEncryption = new DERObjectIdentifier(pkcs_1 + ".13"); + static final DERObjectIdentifier sha224WithRSAEncryption = new DERObjectIdentifier(pkcs_1 + ".14"); + + // + // pkcs-3 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 3 } + // + static final String pkcs_3 = "1.2.840.113549.1.3"; + static final DERObjectIdentifier dhKeyAgreement = new DERObjectIdentifier(pkcs_3 + ".1"); + + // + // pkcs-5 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 5 } + // + static final String pkcs_5 = "1.2.840.113549.1.5"; + + static final DERObjectIdentifier pbeWithMD2AndDES_CBC = new DERObjectIdentifier(pkcs_5 + ".1"); + static final DERObjectIdentifier pbeWithMD2AndRC2_CBC = new DERObjectIdentifier(pkcs_5 + ".4"); + static final DERObjectIdentifier pbeWithMD5AndDES_CBC = new DERObjectIdentifier(pkcs_5 + ".3"); + static final DERObjectIdentifier pbeWithMD5AndRC2_CBC = new DERObjectIdentifier(pkcs_5 + ".6"); + static final DERObjectIdentifier pbeWithSHA1AndDES_CBC = new DERObjectIdentifier(pkcs_5 + ".10"); + static final DERObjectIdentifier pbeWithSHA1AndRC2_CBC = new DERObjectIdentifier(pkcs_5 + ".11"); + + static final DERObjectIdentifier id_PBES2 = new DERObjectIdentifier(pkcs_5 + ".13"); + + static final DERObjectIdentifier id_PBKDF2 = new DERObjectIdentifier(pkcs_5 + ".12"); + + // + // encryptionAlgorithm OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) 3 } + // + static final String encryptionAlgorithm = "1.2.840.113549.3"; + + static final DERObjectIdentifier des_EDE3_CBC = new DERObjectIdentifier(encryptionAlgorithm + ".7"); + static final DERObjectIdentifier RC2_CBC = new DERObjectIdentifier(encryptionAlgorithm + ".2"); + + // + // object identifiers for digests + // + static final String digestAlgorithm = "1.2.840.113549.2"; + // + // md2 OBJECT IDENTIFIER ::= + // {iso(1) member-body(2) US(840) rsadsi(113549) digestAlgorithm(2) 2} + // + static final DERObjectIdentifier md2 = new DERObjectIdentifier(digestAlgorithm + ".2"); + + // + // md4 OBJECT IDENTIFIER ::= + // {iso(1) member-body(2) US(840) rsadsi(113549) digestAlgorithm(2) 4} + // + static final DERObjectIdentifier md4 = new DERObjectIdentifier(digestAlgorithm + ".4"); + + // + // md5 OBJECT IDENTIFIER ::= + // {iso(1) member-body(2) US(840) rsadsi(113549) digestAlgorithm(2) 5} + // + static final DERObjectIdentifier md5 = new DERObjectIdentifier(digestAlgorithm + ".5"); + + static final DERObjectIdentifier id_hmacWithSHA1 = new DERObjectIdentifier(digestAlgorithm + ".7"); + static final DERObjectIdentifier id_hmacWithSHA224 = new DERObjectIdentifier(digestAlgorithm + ".8"); + static final DERObjectIdentifier id_hmacWithSHA256 = new DERObjectIdentifier(digestAlgorithm + ".9"); + static final DERObjectIdentifier id_hmacWithSHA384 = new DERObjectIdentifier(digestAlgorithm + ".10"); + static final DERObjectIdentifier id_hmacWithSHA512 = new DERObjectIdentifier(digestAlgorithm + ".11"); + + // + // pkcs-7 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 7 } + // + static final String pkcs_7 = "1.2.840.113549.1.7"; + static final DERObjectIdentifier data = new DERObjectIdentifier(pkcs_7 + ".1"); + static final DERObjectIdentifier signedData = new DERObjectIdentifier(pkcs_7 + ".2"); + static final DERObjectIdentifier envelopedData = new DERObjectIdentifier(pkcs_7 + ".3"); + static final DERObjectIdentifier signedAndEnvelopedData = new DERObjectIdentifier(pkcs_7 + ".4"); + static final DERObjectIdentifier digestedData = new DERObjectIdentifier(pkcs_7 + ".5"); + static final DERObjectIdentifier encryptedData = new DERObjectIdentifier(pkcs_7 + ".6"); + + // + // pkcs-9 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 9 } + // + static final String pkcs_9 = "1.2.840.113549.1.9"; + + static final DERObjectIdentifier pkcs_9_at_emailAddress = new DERObjectIdentifier(pkcs_9 + ".1"); + static final DERObjectIdentifier pkcs_9_at_unstructuredName = new DERObjectIdentifier(pkcs_9 + ".2"); + static final DERObjectIdentifier pkcs_9_at_contentType = new DERObjectIdentifier(pkcs_9 + ".3"); + static final DERObjectIdentifier pkcs_9_at_messageDigest = new DERObjectIdentifier(pkcs_9 + ".4"); + static final DERObjectIdentifier pkcs_9_at_signingTime = new DERObjectIdentifier(pkcs_9 + ".5"); + static final DERObjectIdentifier pkcs_9_at_counterSignature = new DERObjectIdentifier(pkcs_9 + ".6"); + static final DERObjectIdentifier pkcs_9_at_challengePassword = new DERObjectIdentifier(pkcs_9 + ".7"); + static final DERObjectIdentifier pkcs_9_at_unstructuredAddress = new DERObjectIdentifier(pkcs_9 + ".8"); + static final DERObjectIdentifier pkcs_9_at_extendedCertificateAttributes = new DERObjectIdentifier(pkcs_9 + ".9"); + + static final DERObjectIdentifier pkcs_9_at_signingDescription = new DERObjectIdentifier(pkcs_9 + ".13"); + static final DERObjectIdentifier pkcs_9_at_extensionRequest = new DERObjectIdentifier(pkcs_9 + ".14"); + static final DERObjectIdentifier pkcs_9_at_smimeCapabilities = new DERObjectIdentifier(pkcs_9 + ".15"); + + static final DERObjectIdentifier pkcs_9_at_friendlyName = new DERObjectIdentifier(pkcs_9 + ".20"); + static final DERObjectIdentifier pkcs_9_at_localKeyId = new DERObjectIdentifier(pkcs_9 + ".21"); + + /** @deprecated use x509Certificate instead */ + static final DERObjectIdentifier x509certType = new DERObjectIdentifier(pkcs_9 + ".22.1"); + + static final String certTypes = pkcs_9 + ".22"; + static final DERObjectIdentifier x509Certificate = new DERObjectIdentifier(certTypes + ".1"); + static final DERObjectIdentifier sdsiCertificate = new DERObjectIdentifier(certTypes + ".2"); + + static final String crlTypes = pkcs_9 + ".23"; + static final DERObjectIdentifier x509Crl = new DERObjectIdentifier(crlTypes + ".1"); + + static final DERObjectIdentifier id_alg_PWRI_KEK = new DERObjectIdentifier(pkcs_9 + ".16.3.9"); + + // + // SMIME capability sub oids. + // + static final DERObjectIdentifier preferSignedData = new DERObjectIdentifier(pkcs_9 + ".15.1"); + static final DERObjectIdentifier canNotDecryptAny = new DERObjectIdentifier(pkcs_9 + ".15.2"); + static final DERObjectIdentifier sMIMECapabilitiesVersions = new DERObjectIdentifier(pkcs_9 + ".15.3"); + + // + // id-ct OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840) + // rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) ct(1)} + // + static String id_ct = "1.2.840.113549.1.9.16.1"; + + static final DERObjectIdentifier id_ct_authData = new DERObjectIdentifier(id_ct + ".2"); + static final DERObjectIdentifier id_ct_TSTInfo = new DERObjectIdentifier(id_ct + ".4"); + static final DERObjectIdentifier id_ct_compressedData = new DERObjectIdentifier(id_ct + ".9"); + static final DERObjectIdentifier id_ct_authEnvelopedData = new DERObjectIdentifier(id_ct + ".23"); + + // + // id-cti OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840) + // rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) cti(6)} + // + static String id_cti = "1.2.840.113549.1.9.16.6"; + + static final DERObjectIdentifier id_cti_ets_proofOfOrigin = new DERObjectIdentifier(id_cti + ".1"); + static final DERObjectIdentifier id_cti_ets_proofOfReceipt = new DERObjectIdentifier(id_cti + ".2"); + static final DERObjectIdentifier id_cti_ets_proofOfDelivery = new DERObjectIdentifier(id_cti + ".3"); + static final DERObjectIdentifier id_cti_ets_proofOfSender = new DERObjectIdentifier(id_cti + ".4"); + static final DERObjectIdentifier id_cti_ets_proofOfApproval = new DERObjectIdentifier(id_cti + ".5"); + static final DERObjectIdentifier id_cti_ets_proofOfCreation = new DERObjectIdentifier(id_cti + ".6"); + + // + // id-aa OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840) + // rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) attributes(2)} + // + static String id_aa = "1.2.840.113549.1.9.16.2"; + + static final DERObjectIdentifier id_aa_receiptRequest = new DERObjectIdentifier(id_aa + ".1"); + + static final DERObjectIdentifier id_aa_contentHint = new DERObjectIdentifier(id_aa + ".4"); // See RFC 2634 + /* + * id-aa-encrypKeyPref OBJECT IDENTIFIER ::= {id-aa 11} + * + */ + static final DERObjectIdentifier id_aa_encrypKeyPref = new DERObjectIdentifier(id_aa + ".11"); + static final DERObjectIdentifier id_aa_signingCertificate = new DERObjectIdentifier(id_aa + ".12"); + static final DERObjectIdentifier id_aa_signingCertificateV2 = new DERObjectIdentifier(id_aa + ".47"); + + static final DERObjectIdentifier id_aa_contentIdentifier = new DERObjectIdentifier(id_aa + ".7"); // See RFC 2634 + + /* + * RFC 3126 + */ + static final DERObjectIdentifier id_aa_signatureTimeStampToken = new DERObjectIdentifier(id_aa + ".14"); + + static final DERObjectIdentifier id_aa_ets_sigPolicyId = new DERObjectIdentifier(id_aa + ".15"); + static final DERObjectIdentifier id_aa_ets_commitmentType = new DERObjectIdentifier(id_aa + ".16"); + static final DERObjectIdentifier id_aa_ets_signerLocation = new DERObjectIdentifier(id_aa + ".17"); + static final DERObjectIdentifier id_aa_ets_signerAttr = new DERObjectIdentifier(id_aa + ".18"); + static final DERObjectIdentifier id_aa_ets_otherSigCert = new DERObjectIdentifier(id_aa + ".19"); + static final DERObjectIdentifier id_aa_ets_contentTimestamp = new DERObjectIdentifier(id_aa + ".20"); + static final DERObjectIdentifier id_aa_ets_certificateRefs = new DERObjectIdentifier(id_aa + ".21"); + static final DERObjectIdentifier id_aa_ets_revocationRefs = new DERObjectIdentifier(id_aa + ".22"); + static final DERObjectIdentifier id_aa_ets_certValues = new DERObjectIdentifier(id_aa + ".23"); + static final DERObjectIdentifier id_aa_ets_revocationValues = new DERObjectIdentifier(id_aa + ".24"); + static final DERObjectIdentifier id_aa_ets_escTimeStamp = new DERObjectIdentifier(id_aa + ".25"); + static final DERObjectIdentifier id_aa_ets_certCRLTimestamp = new DERObjectIdentifier(id_aa + ".26"); + static final DERObjectIdentifier id_aa_ets_archiveTimestamp = new DERObjectIdentifier(id_aa + ".27"); + + /** @deprecated use id_aa_ets_sigPolicyId instead */ + static final DERObjectIdentifier id_aa_sigPolicyId = id_aa_ets_sigPolicyId; + /** @deprecated use id_aa_ets_commitmentType instead */ + static final DERObjectIdentifier id_aa_commitmentType = id_aa_ets_commitmentType; + /** @deprecated use id_aa_ets_signerLocation instead */ + static final DERObjectIdentifier id_aa_signerLocation = id_aa_ets_signerLocation; + /** @deprecated use id_aa_ets_otherSigCert instead */ + static final DERObjectIdentifier id_aa_otherSigCert = id_aa_ets_otherSigCert; + + // + // id-spq OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840) + // rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) id-spq(5)} + // + final String id_spq = "1.2.840.113549.1.9.16.5"; + + static final DERObjectIdentifier id_spq_ets_uri = new DERObjectIdentifier(id_spq + ".1"); + static final DERObjectIdentifier id_spq_ets_unotice = new DERObjectIdentifier(id_spq + ".2"); + + // + // pkcs-12 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 12 } + // + static final String pkcs_12 = "1.2.840.113549.1.12"; + static final String bagtypes = pkcs_12 + ".10.1"; + + static final DERObjectIdentifier keyBag = new DERObjectIdentifier(bagtypes + ".1"); + static final DERObjectIdentifier pkcs8ShroudedKeyBag = new DERObjectIdentifier(bagtypes + ".2"); + static final DERObjectIdentifier certBag = new DERObjectIdentifier(bagtypes + ".3"); + static final DERObjectIdentifier crlBag = new DERObjectIdentifier(bagtypes + ".4"); + static final DERObjectIdentifier secretBag = new DERObjectIdentifier(bagtypes + ".5"); + static final DERObjectIdentifier safeContentsBag = new DERObjectIdentifier(bagtypes + ".6"); + + static final String pkcs_12PbeIds = pkcs_12 + ".1"; + + static final DERObjectIdentifier pbeWithSHAAnd128BitRC4 = new DERObjectIdentifier(pkcs_12PbeIds + ".1"); + static final DERObjectIdentifier pbeWithSHAAnd40BitRC4 = new DERObjectIdentifier(pkcs_12PbeIds + ".2"); + static final DERObjectIdentifier pbeWithSHAAnd3_KeyTripleDES_CBC = new DERObjectIdentifier(pkcs_12PbeIds + ".3"); + static final DERObjectIdentifier pbeWithSHAAnd2_KeyTripleDES_CBC = new DERObjectIdentifier(pkcs_12PbeIds + ".4"); + static final DERObjectIdentifier pbeWithSHAAnd128BitRC2_CBC = new DERObjectIdentifier(pkcs_12PbeIds + ".5"); + static final DERObjectIdentifier pbewithSHAAnd40BitRC2_CBC = new DERObjectIdentifier(pkcs_12PbeIds + ".6"); + + static final DERObjectIdentifier id_alg_CMS3DESwrap = new DERObjectIdentifier("1.2.840.113549.1.9.16.3.6"); + static final DERObjectIdentifier id_alg_CMSRC2wrap = new DERObjectIdentifier("1.2.840.113549.1.9.16.3.7"); +} + diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/Pfx.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/Pfx.java new file mode 100644 index 000000000..150eeb53b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/Pfx.java @@ -0,0 +1,71 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.BERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +/** + * the infamous Pfx from PKCS12 + */ +public class Pfx + extends ASN1Encodable + implements PKCSObjectIdentifiers +{ + private ContentInfo contentInfo; + private MacData macData = null; + + public Pfx( + ASN1Sequence seq) + { + BigInteger version = ((DERInteger)seq.getObjectAt(0)).getValue(); + if (version.intValue() != 3) + { + throw new IllegalArgumentException("wrong version for PFX PDU"); + } + + contentInfo = ContentInfo.getInstance(seq.getObjectAt(1)); + + if (seq.size() == 3) + { + macData = MacData.getInstance(seq.getObjectAt(2)); + } + } + + public Pfx( + ContentInfo contentInfo, + MacData macData) + { + this.contentInfo = contentInfo; + this.macData = macData; + } + + public ContentInfo getAuthSafe() + { + return contentInfo; + } + + public MacData getMacData() + { + return macData; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new DERInteger(3)); + v.add(contentInfo); + + if (macData != null) + { + v.add(macData); + } + + return new BERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PrivateKeyInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PrivateKeyInfo.java new file mode 100644 index 000000000..52da65479 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/PrivateKeyInfo.java @@ -0,0 +1,144 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1InputStream; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.Enumeration; + +public class PrivateKeyInfo + extends ASN1Encodable +{ + private DERObject privKey; + private AlgorithmIdentifier algId; + private ASN1Set attributes; + + public static PrivateKeyInfo getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static PrivateKeyInfo getInstance( + Object obj) + { + if (obj instanceof PrivateKeyInfo) + { + return (PrivateKeyInfo)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new PrivateKeyInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public PrivateKeyInfo( + AlgorithmIdentifier algId, + DERObject privateKey) + { + this(algId, privateKey, null); + } + + public PrivateKeyInfo( + AlgorithmIdentifier algId, + DERObject privateKey, + ASN1Set attributes) + { + this.privKey = privateKey; + this.algId = algId; + this.attributes = attributes; + } + + public PrivateKeyInfo( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + BigInteger version = ((DERInteger)e.nextElement()).getValue(); + if (version.intValue() != 0) + { + throw new IllegalArgumentException("wrong version for private key info"); + } + + algId = new AlgorithmIdentifier((ASN1Sequence)e.nextElement()); + + try + { + ASN1InputStream aIn = new ASN1InputStream(((ASN1OctetString)e.nextElement()).getOctets()); + + privKey = aIn.readObject(); + } + catch (IOException ex) + { + throw new IllegalArgumentException("Error recoverying private key from sequence"); + } + + if (e.hasMoreElements()) + { + attributes = ASN1Set.getInstance((ASN1TaggedObject)e.nextElement(), false); + } + } + + public AlgorithmIdentifier getAlgorithmId() + { + return algId; + } + + public DERObject getPrivateKey() + { + return privKey; + } + + public ASN1Set getAttributes() + { + return attributes; + } + + /** + * write out an RSA private key with its associated information + * as described in PKCS8. + *
+     *      PrivateKeyInfo ::= SEQUENCE {
+     *                              version Version,
+     *                              privateKeyAlgorithm AlgorithmIdentifier {{PrivateKeyAlgorithms}},
+     *                              privateKey PrivateKey,
+     *                              attributes [0] IMPLICIT Attributes OPTIONAL 
+     *                          }
+     *      Version ::= INTEGER {v1(0)} (v1,...)
+     *
+     *      PrivateKey ::= OCTET STRING
+     *
+     *      Attributes ::= SET OF Attribute
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new DERInteger(0)); + v.add(algId); + v.add(new DEROctetString(privKey)); + + if (attributes != null) + { + v.add(new DERTaggedObject(false, 0, attributes)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/RC2CBCParameter.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/RC2CBCParameter.java new file mode 100644 index 000000000..0a2e4787f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/RC2CBCParameter.java @@ -0,0 +1,89 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class RC2CBCParameter + extends ASN1Encodable +{ + DERInteger version; + ASN1OctetString iv; + + public static RC2CBCParameter getInstance( + Object o) + { + if (o instanceof ASN1Sequence) + { + return new RC2CBCParameter((ASN1Sequence)o); + } + + throw new IllegalArgumentException("unknown object in RC2CBCParameter factory"); + } + + public RC2CBCParameter( + byte[] iv) + { + this.version = null; + this.iv = new DEROctetString(iv); + } + + public RC2CBCParameter( + int parameterVersion, + byte[] iv) + { + this.version = new DERInteger(parameterVersion); + this.iv = new DEROctetString(iv); + } + + public RC2CBCParameter( + ASN1Sequence seq) + { + if (seq.size() == 1) + { + version = null; + iv = (ASN1OctetString)seq.getObjectAt(0); + } + else + { + version = (DERInteger)seq.getObjectAt(0); + iv = (ASN1OctetString)seq.getObjectAt(1); + } + } + + public BigInteger getRC2ParameterVersion() + { + if (version == null) + { + return null; + } + + return version.getValue(); + } + + public byte[] getIV() + { + return iv.getOctets(); + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (version != null) + { + v.add(version); + } + + v.add(iv); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/RSAESOAEPparams.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/RSAESOAEPparams.java new file mode 100644 index 000000000..b9c5eda42 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/RSAESOAEPparams.java @@ -0,0 +1,151 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERNull; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.oiw.OIWObjectIdentifiers; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class RSAESOAEPparams + extends ASN1Encodable +{ + private AlgorithmIdentifier hashAlgorithm; + private AlgorithmIdentifier maskGenAlgorithm; + private AlgorithmIdentifier pSourceAlgorithm; + + public final static AlgorithmIdentifier DEFAULT_HASH_ALGORITHM = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, new DERNull()); + public final static AlgorithmIdentifier DEFAULT_MASK_GEN_FUNCTION = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, DEFAULT_HASH_ALGORITHM); + public final static AlgorithmIdentifier DEFAULT_P_SOURCE_ALGORITHM = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_pSpecified, new DEROctetString(new byte[0])); + + public static RSAESOAEPparams getInstance( + Object obj) + { + if (obj instanceof RSAESOAEPparams) + { + return (RSAESOAEPparams)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new RSAESOAEPparams((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + /** + * The default version + */ + public RSAESOAEPparams() + { + hashAlgorithm = DEFAULT_HASH_ALGORITHM; + maskGenAlgorithm = DEFAULT_MASK_GEN_FUNCTION; + pSourceAlgorithm = DEFAULT_P_SOURCE_ALGORITHM; + } + + public RSAESOAEPparams( + AlgorithmIdentifier hashAlgorithm, + AlgorithmIdentifier maskGenAlgorithm, + AlgorithmIdentifier pSourceAlgorithm) + { + this.hashAlgorithm = hashAlgorithm; + this.maskGenAlgorithm = maskGenAlgorithm; + this.pSourceAlgorithm = pSourceAlgorithm; + } + + public RSAESOAEPparams( + ASN1Sequence seq) + { + hashAlgorithm = DEFAULT_HASH_ALGORITHM; + maskGenAlgorithm = DEFAULT_MASK_GEN_FUNCTION; + pSourceAlgorithm = DEFAULT_P_SOURCE_ALGORITHM; + + for (int i = 0; i != seq.size(); i++) + { + ASN1TaggedObject o = (ASN1TaggedObject)seq.getObjectAt(i); + + switch (o.getTagNo()) + { + case 0: + hashAlgorithm = AlgorithmIdentifier.getInstance(o, true); + break; + case 1: + maskGenAlgorithm = AlgorithmIdentifier.getInstance(o, true); + break; + case 2: + pSourceAlgorithm = AlgorithmIdentifier.getInstance(o, true); + break; + default: + throw new IllegalArgumentException("unknown tag"); + } + } + } + + public AlgorithmIdentifier getHashAlgorithm() + { + return hashAlgorithm; + } + + public AlgorithmIdentifier getMaskGenAlgorithm() + { + return maskGenAlgorithm; + } + + public AlgorithmIdentifier getPSourceAlgorithm() + { + return pSourceAlgorithm; + } + + /** + *
+     *  RSAES-OAEP-params ::= SEQUENCE {
+     *     hashAlgorithm      [0] OAEP-PSSDigestAlgorithms     DEFAULT sha1,
+     *     maskGenAlgorithm   [1] PKCS1MGFAlgorithms  DEFAULT mgf1SHA1,
+     *     pSourceAlgorithm   [2] PKCS1PSourceAlgorithms  DEFAULT pSpecifiedEmpty
+     *   }
+     *  
+     *   OAEP-PSSDigestAlgorithms    ALGORITHM-IDENTIFIER ::= {
+     *     { OID id-sha1 PARAMETERS NULL   }|
+     *     { OID id-sha256 PARAMETERS NULL }|
+     *     { OID id-sha384 PARAMETERS NULL }|
+     *     { OID id-sha512 PARAMETERS NULL },
+     *     ...  -- Allows for future expansion --
+     *   }
+     *   PKCS1MGFAlgorithms    ALGORITHM-IDENTIFIER ::= {
+     *     { OID id-mgf1 PARAMETERS OAEP-PSSDigestAlgorithms },
+     *    ...  -- Allows for future expansion --
+     *   }
+     *   PKCS1PSourceAlgorithms    ALGORITHM-IDENTIFIER ::= {
+     *     { OID id-pSpecified PARAMETERS OCTET STRING },
+     *     ...  -- Allows for future expansion --
+     *  }
+     * 
+ * @return the asn1 primitive representing the parameters. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (!hashAlgorithm.equals(DEFAULT_HASH_ALGORITHM)) + { + v.add(new DERTaggedObject(true, 0, hashAlgorithm)); + } + + if (!maskGenAlgorithm.equals(DEFAULT_MASK_GEN_FUNCTION)) + { + v.add(new DERTaggedObject(true, 1, maskGenAlgorithm)); + } + + if (!pSourceAlgorithm.equals(DEFAULT_P_SOURCE_ALGORITHM)) + { + v.add(new DERTaggedObject(true, 2, pSourceAlgorithm)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/RSAPrivateKeyStructure.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/RSAPrivateKeyStructure.java new file mode 100644 index 000000000..d33c6aaa0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/RSAPrivateKeyStructure.java @@ -0,0 +1,186 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import java.math.BigInteger; +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class RSAPrivateKeyStructure + extends ASN1Encodable +{ + private int version; + private BigInteger modulus; + private BigInteger publicExponent; + private BigInteger privateExponent; + private BigInteger prime1; + private BigInteger prime2; + private BigInteger exponent1; + private BigInteger exponent2; + private BigInteger coefficient; + private ASN1Sequence otherPrimeInfos = null; + + public static RSAPrivateKeyStructure getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static RSAPrivateKeyStructure getInstance( + Object obj) + { + if (obj instanceof RSAPrivateKeyStructure) + { + return (RSAPrivateKeyStructure)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new RSAPrivateKeyStructure((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public RSAPrivateKeyStructure( + BigInteger modulus, + BigInteger publicExponent, + BigInteger privateExponent, + BigInteger prime1, + BigInteger prime2, + BigInteger exponent1, + BigInteger exponent2, + BigInteger coefficient) + { + this.version = 0; + this.modulus = modulus; + this.publicExponent = publicExponent; + this.privateExponent = privateExponent; + this.prime1 = prime1; + this.prime2 = prime2; + this.exponent1 = exponent1; + this.exponent2 = exponent2; + this.coefficient = coefficient; + } + + public RSAPrivateKeyStructure( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + BigInteger v = ((DERInteger)e.nextElement()).getValue(); + if (v.intValue() != 0 && v.intValue() != 1) + { + throw new IllegalArgumentException("wrong version for RSA private key"); + } + + version = v.intValue(); + modulus = ((DERInteger)e.nextElement()).getValue(); + publicExponent = ((DERInteger)e.nextElement()).getValue(); + privateExponent = ((DERInteger)e.nextElement()).getValue(); + prime1 = ((DERInteger)e.nextElement()).getValue(); + prime2 = ((DERInteger)e.nextElement()).getValue(); + exponent1 = ((DERInteger)e.nextElement()).getValue(); + exponent2 = ((DERInteger)e.nextElement()).getValue(); + coefficient = ((DERInteger)e.nextElement()).getValue(); + + if (e.hasMoreElements()) + { + otherPrimeInfos = (ASN1Sequence)e.nextElement(); + } + } + + public int getVersion() + { + return version; + } + + public BigInteger getModulus() + { + return modulus; + } + + public BigInteger getPublicExponent() + { + return publicExponent; + } + + public BigInteger getPrivateExponent() + { + return privateExponent; + } + + public BigInteger getPrime1() + { + return prime1; + } + + public BigInteger getPrime2() + { + return prime2; + } + + public BigInteger getExponent1() + { + return exponent1; + } + + public BigInteger getExponent2() + { + return exponent2; + } + + public BigInteger getCoefficient() + { + return coefficient; + } + + /** + * This outputs the key in PKCS1v2 format. + *
+     *      RSAPrivateKey ::= SEQUENCE {
+     *                          version Version,
+     *                          modulus INTEGER, -- n
+     *                          publicExponent INTEGER, -- e
+     *                          privateExponent INTEGER, -- d
+     *                          prime1 INTEGER, -- p
+     *                          prime2 INTEGER, -- q
+     *                          exponent1 INTEGER, -- d mod (p-1)
+     *                          exponent2 INTEGER, -- d mod (q-1)
+     *                          coefficient INTEGER, -- (inverse of q) mod p
+     *                          otherPrimeInfos OtherPrimeInfos OPTIONAL
+     *                      }
+     *
+     *      Version ::= INTEGER { two-prime(0), multi(1) }
+     *        (CONSTRAINED BY {-- version must be multi if otherPrimeInfos present --})
+     * 
+ *

+ * This routine is written to output PKCS1 version 2.1, private keys. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new DERInteger(version)); // version + v.add(new DERInteger(getModulus())); + v.add(new DERInteger(getPublicExponent())); + v.add(new DERInteger(getPrivateExponent())); + v.add(new DERInteger(getPrime1())); + v.add(new DERInteger(getPrime2())); + v.add(new DERInteger(getExponent1())); + v.add(new DERInteger(getExponent2())); + v.add(new DERInteger(getCoefficient())); + + if (otherPrimeInfos != null) + { + v.add(otherPrimeInfos); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/RSASSAPSSparams.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/RSASSAPSSparams.java new file mode 100644 index 000000000..c8040037d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/RSASSAPSSparams.java @@ -0,0 +1,170 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERNull; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.oiw.OIWObjectIdentifiers; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class RSASSAPSSparams + extends ASN1Encodable +{ + private AlgorithmIdentifier hashAlgorithm; + private AlgorithmIdentifier maskGenAlgorithm; + private DERInteger saltLength; + private DERInteger trailerField; + + public final static AlgorithmIdentifier DEFAULT_HASH_ALGORITHM = new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1, new DERNull()); + public final static AlgorithmIdentifier DEFAULT_MASK_GEN_FUNCTION = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, DEFAULT_HASH_ALGORITHM); + public final static DERInteger DEFAULT_SALT_LENGTH = new DERInteger(20); + public final static DERInteger DEFAULT_TRAILER_FIELD = new DERInteger(1); + + public static RSASSAPSSparams getInstance( + Object obj) + { + if (obj == null || obj instanceof RSASSAPSSparams) + { + return (RSASSAPSSparams)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new RSASSAPSSparams((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + /** + * The default version + */ + public RSASSAPSSparams() + { + hashAlgorithm = DEFAULT_HASH_ALGORITHM; + maskGenAlgorithm = DEFAULT_MASK_GEN_FUNCTION; + saltLength = DEFAULT_SALT_LENGTH; + trailerField = DEFAULT_TRAILER_FIELD; + } + + public RSASSAPSSparams( + AlgorithmIdentifier hashAlgorithm, + AlgorithmIdentifier maskGenAlgorithm, + DERInteger saltLength, + DERInteger trailerField) + { + this.hashAlgorithm = hashAlgorithm; + this.maskGenAlgorithm = maskGenAlgorithm; + this.saltLength = saltLength; + this.trailerField = trailerField; + } + + public RSASSAPSSparams( + ASN1Sequence seq) + { + hashAlgorithm = DEFAULT_HASH_ALGORITHM; + maskGenAlgorithm = DEFAULT_MASK_GEN_FUNCTION; + saltLength = DEFAULT_SALT_LENGTH; + trailerField = DEFAULT_TRAILER_FIELD; + + for (int i = 0; i != seq.size(); i++) + { + ASN1TaggedObject o = (ASN1TaggedObject)seq.getObjectAt(i); + + switch (o.getTagNo()) + { + case 0: + hashAlgorithm = AlgorithmIdentifier.getInstance(o, true); + break; + case 1: + maskGenAlgorithm = AlgorithmIdentifier.getInstance(o, true); + break; + case 2: + saltLength = DERInteger.getInstance(o, true); + break; + case 3: + trailerField = DERInteger.getInstance(o, true); + break; + default: + throw new IllegalArgumentException("unknown tag"); + } + } + } + + public AlgorithmIdentifier getHashAlgorithm() + { + return hashAlgorithm; + } + + public AlgorithmIdentifier getMaskGenAlgorithm() + { + return maskGenAlgorithm; + } + + public DERInteger getSaltLength() + { + return saltLength; + } + + public DERInteger getTrailerField() + { + return trailerField; + } + + /** + *

+     * RSASSA-PSS-params ::= SEQUENCE {
+     *   hashAlgorithm      [0] OAEP-PSSDigestAlgorithms  DEFAULT sha1,
+     *    maskGenAlgorithm   [1] PKCS1MGFAlgorithms  DEFAULT mgf1SHA1,
+     *    saltLength         [2] INTEGER  DEFAULT 20,
+     *    trailerField       [3] TrailerField  DEFAULT trailerFieldBC
+     *  }
+     *
+     * OAEP-PSSDigestAlgorithms    ALGORITHM-IDENTIFIER ::= {
+     *    { OID id-sha1 PARAMETERS NULL   }|
+     *    { OID id-sha256 PARAMETERS NULL }|
+     *    { OID id-sha384 PARAMETERS NULL }|
+     *    { OID id-sha512 PARAMETERS NULL },
+     *    ...  -- Allows for future expansion --
+     * }
+     *
+     * PKCS1MGFAlgorithms    ALGORITHM-IDENTIFIER ::= {
+     *   { OID id-mgf1 PARAMETERS OAEP-PSSDigestAlgorithms },
+     *    ...  -- Allows for future expansion --
+     * }
+     * 
+     * TrailerField ::= INTEGER { trailerFieldBC(1) }
+     * 
+ * @return the asn1 primitive representing the parameters. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (!hashAlgorithm.equals(DEFAULT_HASH_ALGORITHM)) + { + v.add(new DERTaggedObject(true, 0, hashAlgorithm)); + } + + if (!maskGenAlgorithm.equals(DEFAULT_MASK_GEN_FUNCTION)) + { + v.add(new DERTaggedObject(true, 1, maskGenAlgorithm)); + } + + if (!saltLength.equals(DEFAULT_SALT_LENGTH)) + { + v.add(new DERTaggedObject(true, 2, saltLength)); + } + + if (!trailerField.equals(DEFAULT_TRAILER_FIELD)) + { + v.add(new DERTaggedObject(true, 3, trailerField)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/SafeBag.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/SafeBag.java new file mode 100644 index 000000000..ea22d9f15 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/SafeBag.java @@ -0,0 +1,78 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class SafeBag + extends ASN1Encodable +{ + DERObjectIdentifier bagId; + DERObject bagValue; + ASN1Set bagAttributes; + + public SafeBag( + DERObjectIdentifier oid, + DERObject obj) + { + this.bagId = oid; + this.bagValue = obj; + this.bagAttributes = null; + } + + public SafeBag( + DERObjectIdentifier oid, + DERObject obj, + ASN1Set bagAttributes) + { + this.bagId = oid; + this.bagValue = obj; + this.bagAttributes = bagAttributes; + } + + public SafeBag( + ASN1Sequence seq) + { + this.bagId = (DERObjectIdentifier)seq.getObjectAt(0); + this.bagValue = ((DERTaggedObject)seq.getObjectAt(1)).getObject(); + if (seq.size() == 3) + { + this.bagAttributes = (ASN1Set)seq.getObjectAt(2); + } + } + + public DERObjectIdentifier getBagId() + { + return bagId; + } + + public DERObject getBagValue() + { + return bagValue; + } + + public ASN1Set getBagAttributes() + { + return bagAttributes; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(bagId); + v.add(new DERTaggedObject(0, bagValue)); + + if (bagAttributes != null) + { + v.add(bagAttributes); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/SignedData.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/SignedData.java new file mode 100644 index 000000000..b1811a3c7 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/SignedData.java @@ -0,0 +1,166 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.BERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +/** + * a PKCS#7 signed data object. + */ +public class SignedData + extends ASN1Encodable + implements PKCSObjectIdentifiers +{ + private DERInteger version; + private ASN1Set digestAlgorithms; + private ContentInfo contentInfo; + private ASN1Set certificates; + private ASN1Set crls; + private ASN1Set signerInfos; + + public static SignedData getInstance( + Object o) + { + if (o instanceof SignedData) + { + return (SignedData)o; + } + else if (o instanceof ASN1Sequence) + { + return new SignedData((ASN1Sequence)o); + } + + throw new IllegalArgumentException("unknown object in factory: " + o); + } + + public SignedData( + DERInteger _version, + ASN1Set _digestAlgorithms, + ContentInfo _contentInfo, + ASN1Set _certificates, + ASN1Set _crls, + ASN1Set _signerInfos) + { + version = _version; + digestAlgorithms = _digestAlgorithms; + contentInfo = _contentInfo; + certificates = _certificates; + crls = _crls; + signerInfos = _signerInfos; + } + + public SignedData( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + version = (DERInteger)e.nextElement(); + digestAlgorithms = ((ASN1Set)e.nextElement()); + contentInfo = ContentInfo.getInstance(e.nextElement()); + + while (e.hasMoreElements()) + { + DERObject o = (DERObject)e.nextElement(); + + // + // an interesting feature of SignedData is that there appear to be varying implementations... + // for the moment we ignore anything which doesn't fit. + // + if (o instanceof DERTaggedObject) + { + DERTaggedObject tagged = (DERTaggedObject)o; + + switch (tagged.getTagNo()) + { + case 0: + certificates = ASN1Set.getInstance(tagged, false); + break; + case 1: + crls = ASN1Set.getInstance(tagged, false); + break; + default: + throw new IllegalArgumentException("unknown tag value " + tagged.getTagNo()); + } + } + else + { + signerInfos = (ASN1Set)o; + } + } + } + + public DERInteger getVersion() + { + return version; + } + + public ASN1Set getDigestAlgorithms() + { + return digestAlgorithms; + } + + public ContentInfo getContentInfo() + { + return contentInfo; + } + + public ASN1Set getCertificates() + { + return certificates; + } + + public ASN1Set getCRLs() + { + return crls; + } + + public ASN1Set getSignerInfos() + { + return signerInfos; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  SignedData ::= SEQUENCE {
+     *      version Version,
+     *      digestAlgorithms DigestAlgorithmIdentifiers,
+     *      contentInfo ContentInfo,
+     *      certificates
+     *          [0] IMPLICIT ExtendedCertificatesAndCertificates
+     *                   OPTIONAL,
+     *      crls
+     *          [1] IMPLICIT CertificateRevocationLists OPTIONAL,
+     *      signerInfos SignerInfos }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(digestAlgorithms); + v.add(contentInfo); + + if (certificates != null) + { + v.add(new DERTaggedObject(false, 0, certificates)); + } + + if (crls != null) + { + v.add(new DERTaggedObject(false, 1, crls)); + } + + v.add(signerInfos); + + return new BERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/pkcs/SignerInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/SignerInfo.java new file mode 100644 index 000000000..d7b6472a1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/pkcs/SignerInfo.java @@ -0,0 +1,168 @@ +package com.google.bitcoin.bouncycastle.asn1.pkcs; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.*; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +/** + * a PKCS#7 signer info object. + */ +public class SignerInfo + extends ASN1Encodable +{ + private DERInteger version; + private IssuerAndSerialNumber issuerAndSerialNumber; + private AlgorithmIdentifier digAlgorithm; + private ASN1Set authenticatedAttributes; + private AlgorithmIdentifier digEncryptionAlgorithm; + private ASN1OctetString encryptedDigest; + private ASN1Set unauthenticatedAttributes; + + public static SignerInfo getInstance( + Object o) + { + if (o instanceof SignerInfo) + { + return (SignerInfo)o; + } + else if (o instanceof ASN1Sequence) + { + return new SignerInfo((ASN1Sequence)o); + } + + throw new IllegalArgumentException("unknown object in factory: " + o.getClass().getName()); + } + + public SignerInfo( + DERInteger version, + IssuerAndSerialNumber issuerAndSerialNumber, + AlgorithmIdentifier digAlgorithm, + ASN1Set authenticatedAttributes, + AlgorithmIdentifier digEncryptionAlgorithm, + ASN1OctetString encryptedDigest, + ASN1Set unauthenticatedAttributes) + { + this.version = version; + this.issuerAndSerialNumber = issuerAndSerialNumber; + this.digAlgorithm = digAlgorithm; + this.authenticatedAttributes = authenticatedAttributes; + this.digEncryptionAlgorithm = digEncryptionAlgorithm; + this.encryptedDigest = encryptedDigest; + this.unauthenticatedAttributes = unauthenticatedAttributes; + } + + public SignerInfo( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + version = (DERInteger)e.nextElement(); + issuerAndSerialNumber = IssuerAndSerialNumber.getInstance(e.nextElement()); + digAlgorithm = AlgorithmIdentifier.getInstance(e.nextElement()); + + Object obj = e.nextElement(); + + if (obj instanceof ASN1TaggedObject) + { + authenticatedAttributes = ASN1Set.getInstance((ASN1TaggedObject)obj, false); + + digEncryptionAlgorithm = AlgorithmIdentifier.getInstance(e.nextElement()); + } + else + { + authenticatedAttributes = null; + digEncryptionAlgorithm = AlgorithmIdentifier.getInstance(obj); + } + + encryptedDigest = DEROctetString.getInstance(e.nextElement()); + + if (e.hasMoreElements()) + { + unauthenticatedAttributes = ASN1Set.getInstance((ASN1TaggedObject)e.nextElement(), false); + } + else + { + unauthenticatedAttributes = null; + } + } + + public DERInteger getVersion() + { + return version; + } + + public IssuerAndSerialNumber getIssuerAndSerialNumber() + { + return issuerAndSerialNumber; + } + + public ASN1Set getAuthenticatedAttributes() + { + return authenticatedAttributes; + } + + public AlgorithmIdentifier getDigestAlgorithm() + { + return digAlgorithm; + } + + public ASN1OctetString getEncryptedDigest() + { + return encryptedDigest; + } + + public AlgorithmIdentifier getDigestEncryptionAlgorithm() + { + return digEncryptionAlgorithm; + } + + public ASN1Set getUnauthenticatedAttributes() + { + return unauthenticatedAttributes; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  SignerInfo ::= SEQUENCE {
+     *      version Version,
+     *      issuerAndSerialNumber IssuerAndSerialNumber,
+     *      digestAlgorithm DigestAlgorithmIdentifier,
+     *      authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL,
+     *      digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
+     *      encryptedDigest EncryptedDigest,
+     *      unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
+     *  }
+     *
+     *  EncryptedDigest ::= OCTET STRING
+     *
+     *  DigestAlgorithmIdentifier ::= AlgorithmIdentifier
+     *
+     *  DigestEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(issuerAndSerialNumber); + v.add(digAlgorithm); + + if (authenticatedAttributes != null) + { + v.add(new DERTaggedObject(false, 0, authenticatedAttributes)); + } + + v.add(digEncryptionAlgorithm); + v.add(encryptedDigest); + + if (unauthenticatedAttributes != null) + { + v.add(new DERTaggedObject(false, 1, unauthenticatedAttributes)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/sec/ECPrivateKeyStructure.java b/src/com/google/bitcoin/bouncycastle/asn1/sec/ECPrivateKeyStructure.java new file mode 100644 index 000000000..17b72c51d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/sec/ECPrivateKeyStructure.java @@ -0,0 +1,128 @@ +package com.google.bitcoin.bouncycastle.asn1.sec; + +import java.math.BigInteger; +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Object; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.util.BigIntegers; + +/** + * the elliptic curve private key object from SEC 1 + */ +public class ECPrivateKeyStructure + extends ASN1Encodable +{ + private ASN1Sequence seq; + + public ECPrivateKeyStructure( + ASN1Sequence seq) + { + this.seq = seq; + } + + public ECPrivateKeyStructure( + BigInteger key) + { + byte[] bytes = BigIntegers.asUnsignedByteArray(key); + + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new DERInteger(1)); + v.add(new DEROctetString(bytes)); + + seq = new DERSequence(v); + } + + public ECPrivateKeyStructure( + BigInteger key, + ASN1Encodable parameters) + { + this(key, null, parameters); + } + + public ECPrivateKeyStructure( + BigInteger key, + DERBitString publicKey, + ASN1Encodable parameters) + { + byte[] bytes = BigIntegers.asUnsignedByteArray(key); + + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new DERInteger(1)); + v.add(new DEROctetString(bytes)); + + if (parameters != null) + { + v.add(new DERTaggedObject(true, 0, parameters)); + } + + if (publicKey != null) + { + v.add(new DERTaggedObject(true, 1, publicKey)); + } + + seq = new DERSequence(v); + } + + public BigInteger getKey() + { + ASN1OctetString octs = (ASN1OctetString)seq.getObjectAt(1); + + return new BigInteger(1, octs.getOctets()); + } + + public DERBitString getPublicKey() + { + return (DERBitString)getObjectInTag(1); + } + + public ASN1Object getParameters() + { + return getObjectInTag(0); + } + + private ASN1Object getObjectInTag(int tagNo) + { + Enumeration e = seq.getObjects(); + + while (e.hasMoreElements()) + { + DEREncodable obj = (DEREncodable)e.nextElement(); + + if (obj instanceof ASN1TaggedObject) + { + ASN1TaggedObject tag = (ASN1TaggedObject)obj; + if (tag.getTagNo() == tagNo) + { + return (ASN1Object)((DEREncodable)tag.getObject()).getDERObject(); + } + } + } + return null; + } + + /** + * ECPrivateKey ::= SEQUENCE { + * version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), + * privateKey OCTET STRING, + * parameters [0] Parameters OPTIONAL, + * publicKey [1] BIT STRING OPTIONAL } + */ + public DERObject toASN1Object() + { + return seq; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/sec/SECNamedCurves.java b/src/com/google/bitcoin/bouncycastle/asn1/sec/SECNamedCurves.java new file mode 100644 index 000000000..c8b46124a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/sec/SECNamedCurves.java @@ -0,0 +1,1029 @@ +package com.google.bitcoin.bouncycastle.asn1.sec; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.x9.X9ECParameters; +import com.google.bitcoin.bouncycastle.asn1.x9.X9ECParametersHolder; +import com.google.bitcoin.bouncycastle.math.ec.ECConstants; +import com.google.bitcoin.bouncycastle.math.ec.ECCurve; +import com.google.bitcoin.bouncycastle.math.ec.ECPoint; +import com.google.bitcoin.bouncycastle.util.Strings; +import com.google.bitcoin.bouncycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.Enumeration; +import java.util.Hashtable; + +public class SECNamedCurves +{ + private static BigInteger fromHex( + String hex) + { + return new BigInteger(1, Hex.decode(hex)); + } + + /* + * secp112r1 + */ + static X9ECParametersHolder secp112r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + // p = (2^128 - 3) / 76439 + BigInteger p = fromHex("DB7C2ABF62E35E668076BEAD208B"); + BigInteger a = fromHex("DB7C2ABF62E35E668076BEAD2088"); + BigInteger b = fromHex("659EF8BA043916EEDE8911702B22"); + byte[] S = Hex.decode("00F50B028E4D696E676875615175290472783FB1"); + BigInteger n = fromHex("DB7C2ABF62E35E7628DFAC6561C5"); + BigInteger h = BigInteger.valueOf(1); + + ECCurve curve = new ECCurve.Fp(p, a, b); + //ECPoint G = curve.decodePoint(Hex.decode("02" + //+ "09487239995A5EE76B55F9C2F098")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "09487239995A5EE76B55F9C2F098" + + "A89CE5AF8724C0A23E0E0FF77500")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * secp112r2 + */ + static X9ECParametersHolder secp112r2 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + // p = (2^128 - 3) / 76439 + BigInteger p = fromHex("DB7C2ABF62E35E668076BEAD208B"); + BigInteger a = fromHex("6127C24C05F38A0AAAF65C0EF02C"); + BigInteger b = fromHex("51DEF1815DB5ED74FCC34C85D709"); + byte[] S = Hex.decode("002757A1114D696E6768756151755316C05E0BD4"); + BigInteger n = fromHex("36DF0AAFD8B8D7597CA10520D04B"); + BigInteger h = BigInteger.valueOf(4); + + ECCurve curve = new ECCurve.Fp(p, a, b); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "4BA30AB5E892B4E1649DD0928643")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "4BA30AB5E892B4E1649DD0928643" + + "ADCD46F5882E3747DEF36E956E97")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * secp128r1 + */ + static X9ECParametersHolder secp128r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + // p = 2^128 - 2^97 - 1 + BigInteger p = fromHex("FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFF"); + BigInteger a = fromHex("FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFC"); + BigInteger b = fromHex("E87579C11079F43DD824993C2CEE5ED3"); + byte[] S = Hex.decode("000E0D4D696E6768756151750CC03A4473D03679"); + BigInteger n = fromHex("FFFFFFFE0000000075A30D1B9038A115"); + BigInteger h = BigInteger.valueOf(1); + + ECCurve curve = new ECCurve.Fp(p, a, b); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "161FF7528B899B2D0C28607CA52C5B86")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "161FF7528B899B2D0C28607CA52C5B86" + + "CF5AC8395BAFEB13C02DA292DDED7A83")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * secp128r2 + */ + static X9ECParametersHolder secp128r2 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + // p = 2^128 - 2^97 - 1 + BigInteger p = fromHex("FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFF"); + BigInteger a = fromHex("D6031998D1B3BBFEBF59CC9BBFF9AEE1"); + BigInteger b = fromHex("5EEEFCA380D02919DC2C6558BB6D8A5D"); + byte[] S = Hex.decode("004D696E67687561517512D8F03431FCE63B88F4"); + BigInteger n = fromHex("3FFFFFFF7FFFFFFFBE0024720613B5A3"); + BigInteger h = BigInteger.valueOf(4); + + ECCurve curve = new ECCurve.Fp(p, a, b); + //ECPoint G = curve.decodePoint(Hex.decode("02" + //+ "7B6AA5D85E572983E6FB32A7CDEBC140")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "7B6AA5D85E572983E6FB32A7CDEBC140" + + "27B6916A894D3AEE7106FE805FC34B44")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * secp160k1 + */ + static X9ECParametersHolder secp160k1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + // p = 2^160 - 2^32 - 2^14 - 2^12 - 2^9 - 2^8 - 2^7 - 2^3 - 2^2 - 1 + BigInteger p = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73"); + BigInteger a = ECConstants.ZERO; + BigInteger b = BigInteger.valueOf(7); + byte[] S = null; + BigInteger n = fromHex("0100000000000000000001B8FA16DFAB9ACA16B6B3"); + BigInteger h = BigInteger.valueOf(1); + + ECCurve curve = new ECCurve.Fp(p, a, b); +// ECPoint G = curve.decodePoint(Hex.decode("02" +// + "3B4C382CE37AA192A4019E763036F4F5DD4D7EBB")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "3B4C382CE37AA192A4019E763036F4F5DD4D7EBB" + + "938CF935318FDCED6BC28286531733C3F03C4FEE")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * secp160r1 + */ + static X9ECParametersHolder secp160r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + // p = 2^160 - 2^31 - 1 + BigInteger p = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFF"); + BigInteger a = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFC"); + BigInteger b = fromHex("1C97BEFC54BD7A8B65ACF89F81D4D4ADC565FA45"); + byte[] S = Hex.decode("1053CDE42C14D696E67687561517533BF3F83345"); + BigInteger n = fromHex("0100000000000000000001F4C8F927AED3CA752257"); + BigInteger h = BigInteger.valueOf(1); + + ECCurve curve = new ECCurve.Fp(p, a, b); + //ECPoint G = curve.decodePoint(Hex.decode("02" + //+ "4A96B5688EF573284664698968C38BB913CBFC82")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "4A96B5688EF573284664698968C38BB913CBFC82" + + "23A628553168947D59DCC912042351377AC5FB32")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * secp160r2 + */ + static X9ECParametersHolder secp160r2 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + // p = 2^160 - 2^32 - 2^14 - 2^12 - 2^9 - 2^8 - 2^7 - 2^3 - 2^2 - 1 + BigInteger p = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73"); + BigInteger a = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC70"); + BigInteger b = fromHex("B4E134D3FB59EB8BAB57274904664D5AF50388BA"); + byte[] S = Hex.decode("B99B99B099B323E02709A4D696E6768756151751"); + BigInteger n = fromHex("0100000000000000000000351EE786A818F3A1A16B"); + BigInteger h = BigInteger.valueOf(1); + + ECCurve curve = new ECCurve.Fp(p, a, b); + //ECPoint G = curve.decodePoint(Hex.decode("02" + //+ "52DCB034293A117E1F4FF11B30F7199D3144CE6D")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "52DCB034293A117E1F4FF11B30F7199D3144CE6D" + + "FEAFFEF2E331F296E071FA0DF9982CFEA7D43F2E")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * secp192k1 + */ + static X9ECParametersHolder secp192k1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + // p = 2^192 - 2^32 - 2^12 - 2^8 - 2^7 - 2^6 - 2^3 - 1 + BigInteger p = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37"); + BigInteger a = ECConstants.ZERO; + BigInteger b = BigInteger.valueOf(3); + byte[] S = null; + BigInteger n = fromHex("FFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D"); + BigInteger h = BigInteger.valueOf(1); + + ECCurve curve = new ECCurve.Fp(p, a, b); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D" + + "9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * secp192r1 + */ + static X9ECParametersHolder secp192r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + // p = 2^192 - 2^64 - 1 + BigInteger p = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF"); + BigInteger a = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC"); + BigInteger b = fromHex("64210519E59C80E70FA7E9AB72243049FEB8DEECC146B9B1"); + byte[] S = Hex.decode("3045AE6FC8422F64ED579528D38120EAE12196D5"); + BigInteger n = fromHex("FFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831"); + BigInteger h = BigInteger.valueOf(1); + + ECCurve curve = new ECCurve.Fp(p, a, b); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012" + + "07192B95FFC8DA78631011ED6B24CDD573F977A11E794811")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * secp224k1 + */ + static X9ECParametersHolder secp224k1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + // p = 2^224 - 2^32 - 2^12 - 2^11 - 2^9 - 2^7 - 2^4 - 2 - 1 + BigInteger p = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D"); + BigInteger a = ECConstants.ZERO; + BigInteger b = BigInteger.valueOf(5); + byte[] S = null; + BigInteger n = fromHex("010000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7"); + BigInteger h = BigInteger.valueOf(1); + + ECCurve curve = new ECCurve.Fp(p, a, b); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "A1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "A1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C" + + "7E089FED7FBA344282CAFBD6F7E319F7C0B0BD59E2CA4BDB556D61A5")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * secp224r1 + */ + static X9ECParametersHolder secp224r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + // p = 2^224 - 2^96 + 1 + BigInteger p = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001"); + BigInteger a = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFE"); + BigInteger b = fromHex("B4050A850C04B3ABF54132565044B0B7D7BFD8BA270B39432355FFB4"); + byte[] S = Hex.decode("BD71344799D5C7FCDC45B59FA3B9AB8F6A948BC5"); + BigInteger n = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D"); + BigInteger h = BigInteger.valueOf(1); + + ECCurve curve = new ECCurve.Fp(p, a, b); + //ECPoint G = curve.decodePoint(Hex.decode("02" + //+ "B70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "B70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21" + + "BD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * secp256k1 + */ + static X9ECParametersHolder secp256k1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + // p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 + BigInteger p = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"); + BigInteger a = ECConstants.ZERO; + BigInteger b = BigInteger.valueOf(7); + byte[] S = null; + BigInteger n = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"); + BigInteger h = BigInteger.valueOf(1); + + ECCurve curve = new ECCurve.Fp(p, a, b); + //ECPoint G = curve.decodePoint(Hex.decode("02" + //+ "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" + + "483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * secp256r1 + */ + static X9ECParametersHolder secp256r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + // p = 2^224 (2^32 - 1) + 2^192 + 2^96 - 1 + BigInteger p = fromHex("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF"); + BigInteger a = fromHex("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC"); + BigInteger b = fromHex("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B"); + byte[] S = Hex.decode("C49D360886E704936A6678E1139D26B7819F7E90"); + BigInteger n = fromHex("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"); + BigInteger h = BigInteger.valueOf(1); + + ECCurve curve = new ECCurve.Fp(p, a, b); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296" + + "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * secp384r1 + */ + static X9ECParametersHolder secp384r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + // p = 2^384 - 2^128 - 2^96 + 2^32 - 1 + BigInteger p = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF"); + BigInteger a = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC"); + BigInteger b = fromHex("B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF"); + byte[] S = Hex.decode("A335926AA319A27A1D00896A6773A4827ACDAC73"); + BigInteger n = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973"); + BigInteger h = BigInteger.valueOf(1); + + ECCurve curve = new ECCurve.Fp(p, a, b); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7" + + "3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * secp521r1 + */ + static X9ECParametersHolder secp521r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + // p = 2^521 - 1 + BigInteger p = fromHex("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + BigInteger a = fromHex("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC"); + BigInteger b = fromHex("0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00"); + byte[] S = Hex.decode("D09E8800291CB85396CC6717393284AAA0DA64BA"); + BigInteger n = fromHex("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409"); + BigInteger h = BigInteger.valueOf(1); + + ECCurve curve = new ECCurve.Fp(p, a, b); + //ECPoint G = curve.decodePoint(Hex.decode("02" + //+ "00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66" + + "011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect113r1 + */ + static X9ECParametersHolder sect113r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 113; + int k = 9; + + BigInteger a = fromHex("003088250CA6E7C7FE649CE85820F7"); + BigInteger b = fromHex("00E8BEE4D3E2260744188BE0E9C723"); + byte[] S = Hex.decode("10E723AB14D696E6768756151756FEBF8FCB49A9"); + BigInteger n = fromHex("0100000000000000D9CCEC8A39E56F"); + BigInteger h = BigInteger.valueOf(2); + + ECCurve curve = new ECCurve.F2m(m, k, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "009D73616F35F4AB1407D73562C10F")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "009D73616F35F4AB1407D73562C10F" + + "00A52830277958EE84D1315ED31886")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect113r2 + */ + static X9ECParametersHolder sect113r2 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 113; + int k = 9; + + BigInteger a = fromHex("00689918DBEC7E5A0DD6DFC0AA55C7"); + BigInteger b = fromHex("0095E9A9EC9B297BD4BF36E059184F"); + byte[] S = Hex.decode("10C0FB15760860DEF1EEF4D696E676875615175D"); + BigInteger n = fromHex("010000000000000108789B2496AF93"); + BigInteger h = BigInteger.valueOf(2); + + ECCurve curve = new ECCurve.F2m(m, k, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "01A57A6A7B26CA5EF52FCDB8164797")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "01A57A6A7B26CA5EF52FCDB8164797" + + "00B3ADC94ED1FE674C06E695BABA1D")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect131r1 + */ + static X9ECParametersHolder sect131r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 131; + int k1 = 2; + int k2 = 3; + int k3 = 8; + + BigInteger a = fromHex("07A11B09A76B562144418FF3FF8C2570B8"); + BigInteger b = fromHex("0217C05610884B63B9C6C7291678F9D341"); + byte[] S = Hex.decode("4D696E676875615175985BD3ADBADA21B43A97E2"); + BigInteger n = fromHex("0400000000000000023123953A9464B54D"); + BigInteger h = BigInteger.valueOf(2); + + ECCurve curve = new ECCurve.F2m(m, k1, k2, k3, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "0081BAF91FDF9833C40F9C181343638399")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "0081BAF91FDF9833C40F9C181343638399" + + "078C6E7EA38C001F73C8134B1B4EF9E150")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect131r2 + */ + static X9ECParametersHolder sect131r2 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 131; + int k1 = 2; + int k2 = 3; + int k3 = 8; + + BigInteger a = fromHex("03E5A88919D7CAFCBF415F07C2176573B2"); + BigInteger b = fromHex("04B8266A46C55657AC734CE38F018F2192"); + byte[] S = Hex.decode("985BD3ADBAD4D696E676875615175A21B43A97E3"); + BigInteger n = fromHex("0400000000000000016954A233049BA98F"); + BigInteger h = BigInteger.valueOf(2); + + ECCurve curve = new ECCurve.F2m(m, k1, k2, k3, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "0356DCD8F2F95031AD652D23951BB366A8")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "0356DCD8F2F95031AD652D23951BB366A8" + + "0648F06D867940A5366D9E265DE9EB240F")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect163k1 + */ + static X9ECParametersHolder sect163k1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 163; + int k1 = 3; + int k2 = 6; + int k3 = 7; + + BigInteger a = BigInteger.valueOf(1); + BigInteger b = BigInteger.valueOf(1); + byte[] S = null; + BigInteger n = fromHex("04000000000000000000020108A2E0CC0D99F8A5EF"); + BigInteger h = BigInteger.valueOf(2); + + ECCurve curve = new ECCurve.F2m(m, k1, k2, k3, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "02FE13C0537BBC11ACAA07D793DE4E6D5E5C94EEE8")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "02FE13C0537BBC11ACAA07D793DE4E6D5E5C94EEE8" + + "0289070FB05D38FF58321F2E800536D538CCDAA3D9")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect163r1 + */ + static X9ECParametersHolder sect163r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 163; + int k1 = 3; + int k2 = 6; + int k3 = 7; + + BigInteger a = fromHex("07B6882CAAEFA84F9554FF8428BD88E246D2782AE2"); + BigInteger b = fromHex("0713612DCDDCB40AAB946BDA29CA91F73AF958AFD9"); + byte[] S = Hex.decode("24B7B137C8A14D696E6768756151756FD0DA2E5C"); + BigInteger n = fromHex("03FFFFFFFFFFFFFFFFFFFF48AAB689C29CA710279B"); + BigInteger h = BigInteger.valueOf(2); + + ECCurve curve = new ECCurve.F2m(m, k1, k2, k3, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "0369979697AB43897789566789567F787A7876A654")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "0369979697AB43897789566789567F787A7876A654" + + "00435EDB42EFAFB2989D51FEFCE3C80988F41FF883")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect163r2 + */ + static X9ECParametersHolder sect163r2 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 163; + int k1 = 3; + int k2 = 6; + int k3 = 7; + + BigInteger a = BigInteger.valueOf(1); + BigInteger b = fromHex("020A601907B8C953CA1481EB10512F78744A3205FD"); + byte[] S = Hex.decode("85E25BFE5C86226CDB12016F7553F9D0E693A268"); + BigInteger n = fromHex("040000000000000000000292FE77E70C12A4234C33"); + BigInteger h = BigInteger.valueOf(2); + + ECCurve curve = new ECCurve.F2m(m, k1, k2, k3, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "03F0EBA16286A2D57EA0991168D4994637E8343E36")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "03F0EBA16286A2D57EA0991168D4994637E8343E36" + + "00D51FBC6C71A0094FA2CDD545B11C5C0C797324F1")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect193r1 + */ + static X9ECParametersHolder sect193r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 193; + int k = 15; + + BigInteger a = fromHex("0017858FEB7A98975169E171F77B4087DE098AC8A911DF7B01"); + BigInteger b = fromHex("00FDFB49BFE6C3A89FACADAA7A1E5BBC7CC1C2E5D831478814"); + byte[] S = Hex.decode("103FAEC74D696E676875615175777FC5B191EF30"); + BigInteger n = fromHex("01000000000000000000000000C7F34A778F443ACC920EBA49"); + BigInteger h = BigInteger.valueOf(2); + + ECCurve curve = new ECCurve.F2m(m, k, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "01F481BC5F0FF84A74AD6CDF6FDEF4BF6179625372D8C0C5E1")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "01F481BC5F0FF84A74AD6CDF6FDEF4BF6179625372D8C0C5E1" + + "0025E399F2903712CCF3EA9E3A1AD17FB0B3201B6AF7CE1B05")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect193r2 + */ + static X9ECParametersHolder sect193r2 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 193; + int k = 15; + + BigInteger a = fromHex("0163F35A5137C2CE3EA6ED8667190B0BC43ECD69977702709B"); + BigInteger b = fromHex("00C9BB9E8927D4D64C377E2AB2856A5B16E3EFB7F61D4316AE"); + byte[] S = Hex.decode("10B7B4D696E676875615175137C8A16FD0DA2211"); + BigInteger n = fromHex("010000000000000000000000015AAB561B005413CCD4EE99D5"); + BigInteger h = BigInteger.valueOf(2); + + ECCurve curve = new ECCurve.F2m(m, k, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "00D9B67D192E0367C803F39E1A7E82CA14A651350AAE617E8F")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "00D9B67D192E0367C803F39E1A7E82CA14A651350AAE617E8F" + + "01CE94335607C304AC29E7DEFBD9CA01F596F927224CDECF6C")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect233k1 + */ + static X9ECParametersHolder sect233k1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 233; + int k = 74; + + BigInteger a = ECConstants.ZERO; + BigInteger b = BigInteger.valueOf(1); + byte[] S = null; + BigInteger n = fromHex("8000000000000000000000000000069D5BB915BCD46EFB1AD5F173ABDF"); + BigInteger h = BigInteger.valueOf(4); + + ECCurve curve = new ECCurve.F2m(m, k, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("02" + //+ "017232BA853A7E731AF129F22FF4149563A419C26BF50A4C9D6EEFAD6126")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "017232BA853A7E731AF129F22FF4149563A419C26BF50A4C9D6EEFAD6126" + + "01DB537DECE819B7F70F555A67C427A8CD9BF18AEB9B56E0C11056FAE6A3")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect233r1 + */ + static X9ECParametersHolder sect233r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 233; + int k = 74; + + BigInteger a = BigInteger.valueOf(1); + BigInteger b = fromHex("0066647EDE6C332C7F8C0923BB58213B333B20E9CE4281FE115F7D8F90AD"); + byte[] S = Hex.decode("74D59FF07F6B413D0EA14B344B20A2DB049B50C3"); + BigInteger n = fromHex("01000000000000000000000000000013E974E72F8A6922031D2603CFE0D7"); + BigInteger h = BigInteger.valueOf(2); + + ECCurve curve = new ECCurve.F2m(m, k, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "00FAC9DFCBAC8313BB2139F1BB755FEF65BC391F8B36F8F8EB7371FD558B")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "00FAC9DFCBAC8313BB2139F1BB755FEF65BC391F8B36F8F8EB7371FD558B" + + "01006A08A41903350678E58528BEBF8A0BEFF867A7CA36716F7E01F81052")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect239k1 + */ + static X9ECParametersHolder sect239k1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 239; + int k = 158; + + BigInteger a = ECConstants.ZERO; + BigInteger b = BigInteger.valueOf(1); + byte[] S = null; + BigInteger n = fromHex("2000000000000000000000000000005A79FEC67CB6E91F1C1DA800E478A5"); + BigInteger h = BigInteger.valueOf(4); + + ECCurve curve = new ECCurve.F2m(m, k, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "29A0B6A887A983E9730988A68727A8B2D126C44CC2CC7B2A6555193035DC")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "29A0B6A887A983E9730988A68727A8B2D126C44CC2CC7B2A6555193035DC" + + "76310804F12E549BDB011C103089E73510ACB275FC312A5DC6B76553F0CA")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect283k1 + */ + static X9ECParametersHolder sect283k1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 283; + int k1 = 5; + int k2 = 7; + int k3 = 12; + + BigInteger a = ECConstants.ZERO; + BigInteger b = BigInteger.valueOf(1); + byte[] S = null; + BigInteger n = fromHex("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9AE2ED07577265DFF7F94451E061E163C61"); + BigInteger h = BigInteger.valueOf(4); + + ECCurve curve = new ECCurve.F2m(m, k1, k2, k3, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("02" + //+ "0503213F78CA44883F1A3B8162F188E553CD265F23C1567A16876913B0C2AC2458492836")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "0503213F78CA44883F1A3B8162F188E553CD265F23C1567A16876913B0C2AC2458492836" + + "01CCDA380F1C9E318D90F95D07E5426FE87E45C0E8184698E45962364E34116177DD2259")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect283r1 + */ + static X9ECParametersHolder sect283r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 283; + int k1 = 5; + int k2 = 7; + int k3 = 12; + + BigInteger a = BigInteger.valueOf(1); + BigInteger b = fromHex("027B680AC8B8596DA5A4AF8A19A0303FCA97FD7645309FA2A581485AF6263E313B79A2F5"); + byte[] S = Hex.decode("77E2B07370EB0F832A6DD5B62DFC88CD06BB84BE"); + BigInteger n = fromHex("03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF90399660FC938A90165B042A7CEFADB307"); + BigInteger h = BigInteger.valueOf(2); + + ECCurve curve = new ECCurve.F2m(m, k1, k2, k3, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "05F939258DB7DD90E1934F8C70B0DFEC2EED25B8557EAC9C80E2E198F8CDBECD86B12053")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "05F939258DB7DD90E1934F8C70B0DFEC2EED25B8557EAC9C80E2E198F8CDBECD86B12053" + + "03676854FE24141CB98FE6D4B20D02B4516FF702350EDDB0826779C813F0DF45BE8112F4")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect409k1 + */ + static X9ECParametersHolder sect409k1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 409; + int k = 87; + + BigInteger a = ECConstants.ZERO; + BigInteger b = BigInteger.valueOf(1); + byte[] S = null; + BigInteger n = fromHex("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5F83B2D4EA20400EC4557D5ED3E3E7CA5B4B5C83B8E01E5FCF"); + BigInteger h = BigInteger.valueOf(4); + + ECCurve curve = new ECCurve.F2m(m, k, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "0060F05F658F49C1AD3AB1890F7184210EFD0987E307C84C27ACCFB8F9F67CC2C460189EB5AAAA62EE222EB1B35540CFE9023746")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "0060F05F658F49C1AD3AB1890F7184210EFD0987E307C84C27ACCFB8F9F67CC2C460189EB5AAAA62EE222EB1B35540CFE9023746" + + "01E369050B7C4E42ACBA1DACBF04299C3460782F918EA427E6325165E9EA10E3DA5F6C42E9C55215AA9CA27A5863EC48D8E0286B")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect409r1 + */ + static X9ECParametersHolder sect409r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 409; + int k = 87; + + BigInteger a = BigInteger.valueOf(1); + BigInteger b = fromHex("0021A5C2C8EE9FEB5C4B9A753B7B476B7FD6422EF1F3DD674761FA99D6AC27C8A9A197B272822F6CD57A55AA4F50AE317B13545F"); + byte[] S = Hex.decode("4099B5A457F9D69F79213D094C4BCD4D4262210B"); + BigInteger n = fromHex("010000000000000000000000000000000000000000000000000001E2AAD6A612F33307BE5FA47C3C9E052F838164CD37D9A21173"); + BigInteger h = BigInteger.valueOf(2); + + ECCurve curve = new ECCurve.F2m(m, k, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "015D4860D088DDB3496B0C6064756260441CDE4AF1771D4DB01FFE5B34E59703DC255A868A1180515603AEAB60794E54BB7996A7")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "015D4860D088DDB3496B0C6064756260441CDE4AF1771D4DB01FFE5B34E59703DC255A868A1180515603AEAB60794E54BB7996A7" + + "0061B1CFAB6BE5F32BBFA78324ED106A7636B9C5A7BD198D0158AA4F5488D08F38514F1FDF4B4F40D2181B3681C364BA0273C706")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect571k1 + */ + static X9ECParametersHolder sect571k1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 571; + int k1 = 2; + int k2 = 5; + int k3 = 10; + + BigInteger a = ECConstants.ZERO; + BigInteger b = BigInteger.valueOf(1); + byte[] S = null; + BigInteger n = fromHex("020000000000000000000000000000000000000000000000000000000000000000000000131850E1F19A63E4B391A8DB917F4138B630D84BE5D639381E91DEB45CFE778F637C1001"); + BigInteger h = BigInteger.valueOf(4); + + ECCurve curve = new ECCurve.F2m(m, k1, k2, k3, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("02" + //+ "026EB7A859923FBC82189631F8103FE4AC9CA2970012D5D46024804801841CA44370958493B205E647DA304DB4CEB08CBBD1BA39494776FB988B47174DCA88C7E2945283A01C8972")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "026EB7A859923FBC82189631F8103FE4AC9CA2970012D5D46024804801841CA44370958493B205E647DA304DB4CEB08CBBD1BA39494776FB988B47174DCA88C7E2945283A01C8972" + + "0349DC807F4FBF374F4AEADE3BCA95314DD58CEC9F307A54FFC61EFC006D8A2C9D4979C0AC44AEA74FBEBBB9F772AEDCB620B01A7BA7AF1B320430C8591984F601CD4C143EF1C7A3")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + /* + * sect571r1 + */ + static X9ECParametersHolder sect571r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + int m = 571; + int k1 = 2; + int k2 = 5; + int k3 = 10; + + BigInteger a = BigInteger.valueOf(1); + BigInteger b = fromHex("02F40E7E2221F295DE297117B7F3D62F5C6A97FFCB8CEFF1CD6BA8CE4A9A18AD84FFABBD8EFA59332BE7AD6756A66E294AFD185A78FF12AA520E4DE739BACA0C7FFEFF7F2955727A"); + byte[] S = Hex.decode("2AA058F73A0E33AB486B0F610410C53A7F132310"); + BigInteger n = fromHex("03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE661CE18FF55987308059B186823851EC7DD9CA1161DE93D5174D66E8382E9BB2FE84E47"); + BigInteger h = BigInteger.valueOf(2); + + ECCurve curve = new ECCurve.F2m(m, k1, k2, k3, a, b, n, h); + //ECPoint G = curve.decodePoint(Hex.decode("03" + //+ "0303001D34B856296C16C0D40D3CD7750A93D1D2955FA80AA5F40FC8DB7B2ABDBDE53950F4C0D293CDD711A35B67FB1499AE60038614F1394ABFA3B4C850D927E1E7769C8EEC2D19")); + ECPoint G = curve.decodePoint(Hex.decode("04" + + "0303001D34B856296C16C0D40D3CD7750A93D1D2955FA80AA5F40FC8DB7B2ABDBDE53950F4C0D293CDD711A35B67FB1499AE60038614F1394ABFA3B4C850D927E1E7769C8EEC2D19" + + "037BF27342DA639B6DCCFFFEB73D69D78C6C27A6009CBBCA1980F8533921E8A684423E43BAB08A576291AF8F461BB2A8B3531D2F0485C19B16E2F1516E23DD3C1A4827AF1B8AC15B")); + + return new X9ECParameters(curve, G, n, h, S); + } + }; + + + static final Hashtable objIds = new Hashtable(); + static final Hashtable curves = new Hashtable(); + static final Hashtable names = new Hashtable(); + + static void defineCurve(String name, DERObjectIdentifier oid, X9ECParametersHolder holder) + { + objIds.put(name, oid); + names.put(oid, name); + curves.put(oid, holder); + } + + static + { + defineCurve("secp112r1", SECObjectIdentifiers.secp112r1, secp112r1); + defineCurve("secp112r2", SECObjectIdentifiers.secp112r2, secp112r2); + defineCurve("secp128r1", SECObjectIdentifiers.secp128r1, secp128r1); + defineCurve("secp128r2", SECObjectIdentifiers.secp128r2, secp128r2); + defineCurve("secp160k1", SECObjectIdentifiers.secp160k1, secp160k1); + defineCurve("secp160r1", SECObjectIdentifiers.secp160r1, secp160r1); + defineCurve("secp160r2", SECObjectIdentifiers.secp160r2, secp160r2); + defineCurve("secp192k1", SECObjectIdentifiers.secp192k1, secp192k1); + defineCurve("secp192r1", SECObjectIdentifiers.secp192r1, secp192r1); + defineCurve("secp224k1", SECObjectIdentifiers.secp224k1, secp224k1); + defineCurve("secp224r1", SECObjectIdentifiers.secp224r1, secp224r1); + defineCurve("secp256k1", SECObjectIdentifiers.secp256k1, secp256k1); + defineCurve("secp256r1", SECObjectIdentifiers.secp256r1, secp256r1); + defineCurve("secp384r1", SECObjectIdentifiers.secp384r1, secp384r1); + defineCurve("secp521r1", SECObjectIdentifiers.secp521r1, secp521r1); + + defineCurve("sect113r1", SECObjectIdentifiers.sect113r1, sect113r1); + defineCurve("sect113r2", SECObjectIdentifiers.sect113r2, sect113r2); + defineCurve("sect131r1", SECObjectIdentifiers.sect131r1, sect131r1); + defineCurve("sect131r2", SECObjectIdentifiers.sect131r2, sect131r2); + defineCurve("sect163k1", SECObjectIdentifiers.sect163k1, sect163k1); + defineCurve("sect163r1", SECObjectIdentifiers.sect163r1, sect163r1); + defineCurve("sect163r2", SECObjectIdentifiers.sect163r2, sect163r2); + defineCurve("sect193r1", SECObjectIdentifiers.sect193r1, sect193r1); + defineCurve("sect193r2", SECObjectIdentifiers.sect193r2, sect193r2); + defineCurve("sect233k1", SECObjectIdentifiers.sect233k1, sect233k1); + defineCurve("sect233r1", SECObjectIdentifiers.sect233r1, sect233r1); + defineCurve("sect239k1", SECObjectIdentifiers.sect239k1, sect239k1); + defineCurve("sect283k1", SECObjectIdentifiers.sect283k1, sect283k1); + defineCurve("sect283r1", SECObjectIdentifiers.sect283r1, sect283r1); + defineCurve("sect409k1", SECObjectIdentifiers.sect409k1, sect409k1); + defineCurve("sect409r1", SECObjectIdentifiers.sect409r1, sect409r1); + defineCurve("sect571k1", SECObjectIdentifiers.sect571k1, sect571k1); + defineCurve("sect571r1", SECObjectIdentifiers.sect571r1, sect571r1); + } + + public static X9ECParameters getByName( + String name) + { + DERObjectIdentifier oid = (DERObjectIdentifier)objIds.get(Strings.toLowerCase(name)); + + if (oid != null) + { + return getByOID(oid); + } + + return null; + } + + /** + * return the X9ECParameters object for the named curve represented by + * the passed in object identifier. Null if the curve isn't present. + * + * @param oid an object identifier representing a named curve, if present. + */ + public static X9ECParameters getByOID( + DERObjectIdentifier oid) + { + X9ECParametersHolder holder = (X9ECParametersHolder)curves.get(oid); + + if (holder != null) + { + return holder.getParameters(); + } + + return null; + } + + /** + * return the object identifier signified by the passed in name. Null + * if there is no object identifier associated with name. + * + * @return the object identifier associated with name, if present. + */ + public static DERObjectIdentifier getOID( + String name) + { + return (DERObjectIdentifier)objIds.get(Strings.toLowerCase(name)); + } + + /** + * return the named curve name represented by the given object identifier. + */ + public static String getName( + DERObjectIdentifier oid) + { + return (String)names.get(oid); + } + + /** + * returns an enumeration containing the name strings for curves + * contained in this structure. + */ + public static Enumeration getNames() + { + return objIds.keys(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/sec/SECObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/sec/SECObjectIdentifiers.java new file mode 100644 index 000000000..2d2028730 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/sec/SECObjectIdentifiers.java @@ -0,0 +1,50 @@ +package com.google.bitcoin.bouncycastle.asn1.sec; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.x9.X9ObjectIdentifiers; + +public interface SECObjectIdentifiers +{ + /** + * ellipticCurve OBJECT IDENTIFIER ::= { + * iso(1) identified-organization(3) certicom(132) curve(0) + * } + */ + static final DERObjectIdentifier ellipticCurve = new DERObjectIdentifier("1.3.132.0"); + + static final DERObjectIdentifier sect163k1 = new DERObjectIdentifier(ellipticCurve + ".1"); + static final DERObjectIdentifier sect163r1 = new DERObjectIdentifier(ellipticCurve + ".2"); + static final DERObjectIdentifier sect239k1 = new DERObjectIdentifier(ellipticCurve + ".3"); + static final DERObjectIdentifier sect113r1 = new DERObjectIdentifier(ellipticCurve + ".4"); + static final DERObjectIdentifier sect113r2 = new DERObjectIdentifier(ellipticCurve + ".5"); + static final DERObjectIdentifier secp112r1 = new DERObjectIdentifier(ellipticCurve + ".6"); + static final DERObjectIdentifier secp112r2 = new DERObjectIdentifier(ellipticCurve + ".7"); + static final DERObjectIdentifier secp160r1 = new DERObjectIdentifier(ellipticCurve + ".8"); + static final DERObjectIdentifier secp160k1 = new DERObjectIdentifier(ellipticCurve + ".9"); + static final DERObjectIdentifier secp256k1 = new DERObjectIdentifier(ellipticCurve + ".10"); + static final DERObjectIdentifier sect163r2 = new DERObjectIdentifier(ellipticCurve + ".15"); + static final DERObjectIdentifier sect283k1 = new DERObjectIdentifier(ellipticCurve + ".16"); + static final DERObjectIdentifier sect283r1 = new DERObjectIdentifier(ellipticCurve + ".17"); + static final DERObjectIdentifier sect131r1 = new DERObjectIdentifier(ellipticCurve + ".22"); + static final DERObjectIdentifier sect131r2 = new DERObjectIdentifier(ellipticCurve + ".23"); + static final DERObjectIdentifier sect193r1 = new DERObjectIdentifier(ellipticCurve + ".24"); + static final DERObjectIdentifier sect193r2 = new DERObjectIdentifier(ellipticCurve + ".25"); + static final DERObjectIdentifier sect233k1 = new DERObjectIdentifier(ellipticCurve + ".26"); + static final DERObjectIdentifier sect233r1 = new DERObjectIdentifier(ellipticCurve + ".27"); + static final DERObjectIdentifier secp128r1 = new DERObjectIdentifier(ellipticCurve + ".28"); + static final DERObjectIdentifier secp128r2 = new DERObjectIdentifier(ellipticCurve + ".29"); + static final DERObjectIdentifier secp160r2 = new DERObjectIdentifier(ellipticCurve + ".30"); + static final DERObjectIdentifier secp192k1 = new DERObjectIdentifier(ellipticCurve + ".31"); + static final DERObjectIdentifier secp224k1 = new DERObjectIdentifier(ellipticCurve + ".32"); + static final DERObjectIdentifier secp224r1 = new DERObjectIdentifier(ellipticCurve + ".33"); + static final DERObjectIdentifier secp384r1 = new DERObjectIdentifier(ellipticCurve + ".34"); + static final DERObjectIdentifier secp521r1 = new DERObjectIdentifier(ellipticCurve + ".35"); + static final DERObjectIdentifier sect409k1 = new DERObjectIdentifier(ellipticCurve + ".36"); + static final DERObjectIdentifier sect409r1 = new DERObjectIdentifier(ellipticCurve + ".37"); + static final DERObjectIdentifier sect571k1 = new DERObjectIdentifier(ellipticCurve + ".38"); + static final DERObjectIdentifier sect571r1 = new DERObjectIdentifier(ellipticCurve + ".39"); + + static final DERObjectIdentifier secp192r1 = X9ObjectIdentifiers.prime192v1; + static final DERObjectIdentifier secp256r1 = X9ObjectIdentifiers.prime256v1; + +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMEAttributes.java b/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMEAttributes.java new file mode 100644 index 000000000..fd3c872e1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMEAttributes.java @@ -0,0 +1,10 @@ +package com.google.bitcoin.bouncycastle.asn1.smime; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; + +public interface SMIMEAttributes +{ + public static final DERObjectIdentifier smimeCapabilities = PKCSObjectIdentifiers.pkcs_9_at_smimeCapabilities; + public static final DERObjectIdentifier encrypKeyPref = PKCSObjectIdentifiers.id_aa_encrypKeyPref; +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapabilities.java b/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapabilities.java new file mode 100644 index 000000000..e24c1488e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapabilities.java @@ -0,0 +1,115 @@ +package com.google.bitcoin.bouncycastle.asn1.smime; + +import java.util.Enumeration; +import java.util.Vector; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.cms.Attribute; +import com.google.bitcoin.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; + +/** + * Handler class for dealing with S/MIME Capabilities + */ +public class SMIMECapabilities + extends ASN1Encodable +{ + /** + * general preferences + */ + public static final DERObjectIdentifier preferSignedData = PKCSObjectIdentifiers.preferSignedData; + public static final DERObjectIdentifier canNotDecryptAny = PKCSObjectIdentifiers.canNotDecryptAny; + public static final DERObjectIdentifier sMIMECapabilitesVersions = PKCSObjectIdentifiers.sMIMECapabilitiesVersions; + + /** + * encryption algorithms preferences + */ + public static final DERObjectIdentifier dES_CBC = new DERObjectIdentifier("1.3.14.3.2.7"); + public static final DERObjectIdentifier dES_EDE3_CBC = PKCSObjectIdentifiers.des_EDE3_CBC; + public static final DERObjectIdentifier rC2_CBC = PKCSObjectIdentifiers.RC2_CBC; + + private ASN1Sequence capabilities; + + /** + * return an Attribute object from the given object. + * + * @param o the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static SMIMECapabilities getInstance( + Object o) + { + if (o == null || o instanceof SMIMECapabilities) + { + return (SMIMECapabilities)o; + } + + if (o instanceof ASN1Sequence) + { + return new SMIMECapabilities((ASN1Sequence)o); + } + + if (o instanceof Attribute) + { + return new SMIMECapabilities( + (ASN1Sequence)(((Attribute)o).getAttrValues().getObjectAt(0))); + } + + throw new IllegalArgumentException("unknown object in factory: " + o.getClass().getName()); + } + + public SMIMECapabilities( + ASN1Sequence seq) + { + capabilities = seq; + } + + /** + * returns a vector with 0 or more objects of all the capabilities + * matching the passed in capability OID. If the OID passed is null the + * entire set is returned. + */ + public Vector getCapabilities( + DERObjectIdentifier capability) + { + Enumeration e = capabilities.getObjects(); + Vector list = new Vector(); + + if (capability == null) + { + while (e.hasMoreElements()) + { + SMIMECapability cap = SMIMECapability.getInstance(e.nextElement()); + + list.addElement(cap); + } + } + else + { + while (e.hasMoreElements()) + { + SMIMECapability cap = SMIMECapability.getInstance(e.nextElement()); + + if (capability.equals(cap.getCapabilityID())) + { + list.addElement(cap); + } + } + } + + return list; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * SMIMECapabilities ::= SEQUENCE OF SMIMECapability
+     * 
+ */ + public DERObject toASN1Object() + { + return capabilities; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapabilitiesAttribute.java b/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapabilitiesAttribute.java new file mode 100644 index 000000000..a8620aa93 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapabilitiesAttribute.java @@ -0,0 +1,16 @@ +package com.google.bitcoin.bouncycastle.asn1.smime; + +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERSet; +import com.google.bitcoin.bouncycastle.asn1.cms.Attribute; + +public class SMIMECapabilitiesAttribute + extends Attribute +{ + public SMIMECapabilitiesAttribute( + SMIMECapabilityVector capabilities) + { + super(SMIMEAttributes.smimeCapabilities, + new DERSet(new DERSequence(capabilities.toDEREncodableVector()))); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapability.java b/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapability.java new file mode 100644 index 000000000..9ed39cea0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapability.java @@ -0,0 +1,103 @@ +package com.google.bitcoin.bouncycastle.asn1.smime; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.nist.NISTObjectIdentifiers; +import com.google.bitcoin.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; + +public class SMIMECapability + extends ASN1Encodable +{ + /** + * general preferences + */ + public static final DERObjectIdentifier preferSignedData = PKCSObjectIdentifiers.preferSignedData; + public static final DERObjectIdentifier canNotDecryptAny = PKCSObjectIdentifiers.canNotDecryptAny; + public static final DERObjectIdentifier sMIMECapabilitiesVersions = PKCSObjectIdentifiers.sMIMECapabilitiesVersions; + + /** + * encryption algorithms preferences + */ + public static final DERObjectIdentifier dES_CBC = new DERObjectIdentifier("1.3.14.3.2.7"); + public static final DERObjectIdentifier dES_EDE3_CBC = PKCSObjectIdentifiers.des_EDE3_CBC; + public static final DERObjectIdentifier rC2_CBC = PKCSObjectIdentifiers.RC2_CBC; + public static final DERObjectIdentifier aES128_CBC = NISTObjectIdentifiers.id_aes128_CBC; + public static final DERObjectIdentifier aES192_CBC = NISTObjectIdentifiers.id_aes192_CBC; + public static final DERObjectIdentifier aES256_CBC = NISTObjectIdentifiers.id_aes256_CBC; + + private DERObjectIdentifier capabilityID; + private DEREncodable parameters; + + public SMIMECapability( + ASN1Sequence seq) + { + capabilityID = (DERObjectIdentifier)seq.getObjectAt(0); + + if (seq.size() > 1) + { + parameters = (DERObject)seq.getObjectAt(1); + } + } + + public SMIMECapability( + DERObjectIdentifier capabilityID, + DEREncodable parameters) + { + this.capabilityID = capabilityID; + this.parameters = parameters; + } + + public static SMIMECapability getInstance( + Object obj) + { + if (obj == null || obj instanceof SMIMECapability) + { + return (SMIMECapability)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new SMIMECapability((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid SMIMECapability"); + } + + public DERObjectIdentifier getCapabilityID() + { + return capabilityID; + } + + public DEREncodable getParameters() + { + return parameters; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
 
+     * SMIMECapability ::= SEQUENCE {
+     *     capabilityID OBJECT IDENTIFIER,
+     *     parameters ANY DEFINED BY capabilityID OPTIONAL 
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(capabilityID); + + if (parameters != null) + { + v.add(parameters); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapabilityVector.java b/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapabilityVector.java new file mode 100644 index 000000000..f5aff6bee --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMECapabilityVector.java @@ -0,0 +1,51 @@ +package com.google.bitcoin.bouncycastle.asn1.smime; + +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DEREncodableVector; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * Handler for creating a vector S/MIME Capabilities + */ +public class SMIMECapabilityVector +{ + private ASN1EncodableVector capabilities = new ASN1EncodableVector(); + + public void addCapability( + DERObjectIdentifier capability) + { + capabilities.add(new DERSequence(capability)); + } + + public void addCapability( + DERObjectIdentifier capability, + int value) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(capability); + v.add(new DERInteger(value)); + + capabilities.add(new DERSequence(v)); + } + + public void addCapability( + DERObjectIdentifier capability, + DEREncodable params) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(capability); + v.add(params); + + capabilities.add(new DERSequence(v)); + } + + public DEREncodableVector toDEREncodableVector() + { + return capabilities; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMEEncryptionKeyPreferenceAttribute.java b/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMEEncryptionKeyPreferenceAttribute.java new file mode 100644 index 000000000..f6358a62b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/smime/SMIMEEncryptionKeyPreferenceAttribute.java @@ -0,0 +1,48 @@ +package com.google.bitcoin.bouncycastle.asn1.smime; + +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSet; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.cms.Attribute; +import com.google.bitcoin.bouncycastle.asn1.cms.IssuerAndSerialNumber; +import com.google.bitcoin.bouncycastle.asn1.cms.RecipientKeyIdentifier; + +/** + * The SMIMEEncryptionKeyPreference object. + *
+ * SMIMEEncryptionKeyPreference ::= CHOICE {
+ *     issuerAndSerialNumber   [0] IssuerAndSerialNumber,
+ *     receipentKeyId          [1] RecipientKeyIdentifier,
+ *     subjectAltKeyIdentifier [2] SubjectKeyIdentifier
+ * }
+ * 
+ */ +public class SMIMEEncryptionKeyPreferenceAttribute + extends Attribute +{ + public SMIMEEncryptionKeyPreferenceAttribute( + IssuerAndSerialNumber issAndSer) + { + super(SMIMEAttributes.encrypKeyPref, + new DERSet(new DERTaggedObject(false, 0, issAndSer))); + } + + public SMIMEEncryptionKeyPreferenceAttribute( + RecipientKeyIdentifier rKeyId) + { + + super(SMIMEAttributes.encrypKeyPref, + new DERSet(new DERTaggedObject(false, 1, rKeyId))); + } + + /** + * @param sKeyId the subjectKeyIdentifier value (normally the X.509 one) + */ + public SMIMEEncryptionKeyPreferenceAttribute( + ASN1OctetString sKeyId) + { + + super(SMIMEAttributes.encrypKeyPref, + new DERSet(new DERTaggedObject(false, 2, sKeyId))); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/teletrust/TeleTrusTNamedCurves.java b/src/com/google/bitcoin/bouncycastle/asn1/teletrust/TeleTrusTNamedCurves.java new file mode 100644 index 000000000..5e77f5503 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/teletrust/TeleTrusTNamedCurves.java @@ -0,0 +1,351 @@ +package com.google.bitcoin.bouncycastle.asn1.teletrust; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.x9.X9ECParameters; +import com.google.bitcoin.bouncycastle.asn1.x9.X9ECParametersHolder; +import com.google.bitcoin.bouncycastle.math.ec.ECCurve; +import com.google.bitcoin.bouncycastle.util.Strings; +import com.google.bitcoin.bouncycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.Enumeration; +import java.util.Hashtable; + +/** + * elliptic curves defined in "ECC Brainpool Standard Curves and Curve Generation" + * http://www.ecc-brainpool.org/download/draft_pkix_additional_ecc_dp.txt + */ +public class TeleTrusTNamedCurves +{ + static X9ECParametersHolder brainpoolP160r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve curve = new ECCurve.Fp( + new BigInteger("E95E4A5F737059DC60DFC7AD95B3D8139515620F", 16), // q + new BigInteger("340E7BE2A280EB74E2BE61BADA745D97E8F7C300", 16), // a + new BigInteger("1E589A8595423412134FAA2DBDEC95C8D8675E58", 16)); // b + + return new X9ECParameters( + curve, + curve.decodePoint(Hex.decode("04BED5AF16EA3F6A4F62938C4631EB5AF7BDBCDBC31667CB477A1A8EC338F94741669C976316DA6321")), // G + new BigInteger("E95E4A5F737059DC60DF5991D45029409E60FC09", 16), //n + new BigInteger("01", 16)); // h + } + }; + + static X9ECParametersHolder brainpoolP160t1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve curve = new ECCurve.Fp( + // new BigInteger("24DBFF5DEC9B986BBFE5295A29BFBAE45E0F5D0B", 16), // Z + new BigInteger("E95E4A5F737059DC60DFC7AD95B3D8139515620F", 16), // q + new BigInteger("E95E4A5F737059DC60DFC7AD95B3D8139515620C", 16), // a' + new BigInteger("7A556B6DAE535B7B51ED2C4D7DAA7A0B5C55F380", 16)); // b' + + return new X9ECParameters( + curve, + curve.decodePoint(Hex.decode("04B199B13B9B34EFC1397E64BAEB05ACC265FF2378ADD6718B7C7C1961F0991B842443772152C9E0AD")), // G + new BigInteger("E95E4A5F737059DC60DF5991D45029409E60FC09", 16), //n + new BigInteger("01", 16)); // h + } + }; + + static X9ECParametersHolder brainpoolP192r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve curve = new ECCurve.Fp( + new BigInteger("C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86297", 16), // q + new BigInteger("6A91174076B1E0E19C39C031FE8685C1CAE040E5C69A28EF", 16), // a + new BigInteger("469A28EF7C28CCA3DC721D044F4496BCCA7EF4146FBF25C9", 16)); // b + + return new X9ECParameters( + curve, + curve.decodePoint(Hex.decode("04C0A0647EAAB6A48753B033C56CB0F0900A2F5C4853375FD614B690866ABD5BB88B5F4828C1490002E6773FA2FA299B8F")), // G + new BigInteger("C302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1", 16), //n + new BigInteger("01", 16)); // h + } + }; + + static X9ECParametersHolder brainpoolP192t1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve curve = new ECCurve.Fp( + //new BigInteger("1B6F5CC8DB4DC7AF19458A9CB80DC2295E5EB9C3732104CB") //Z + new BigInteger("C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86297", 16), // q + new BigInteger("C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86294", 16), // a' + new BigInteger("13D56FFAEC78681E68F9DEB43B35BEC2FB68542E27897B79", 16)); // b' + + return new X9ECParameters( + curve, + curve.decodePoint(Hex.decode("043AE9E58C82F63C30282E1FE7BBF43FA72C446AF6F4618129097E2C5667C2223A902AB5CA449D0084B7E5B3DE7CCC01C9")), // G' + new BigInteger("C302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1", 16), //n + new BigInteger("01", 16)); // h + } + }; + + static X9ECParametersHolder brainpoolP224r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve curve = new ECCurve.Fp( + new BigInteger("D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF", 16), // q + new BigInteger("68A5E62CA9CE6C1C299803A6C1530B514E182AD8B0042A59CAD29F43", 16), // a + new BigInteger("2580F63CCFE44138870713B1A92369E33E2135D266DBB372386C400B", 16)); // b + + return new X9ECParameters( + curve, + curve.decodePoint(Hex.decode("040D9029AD2C7E5CF4340823B2A87DC68C9E4CE3174C1E6EFDEE12C07D58AA56F772C0726F24C6B89E4ECDAC24354B9E99CAA3F6D3761402CD")), // G + new BigInteger("D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F", 16), //n + new BigInteger("01", 16)); // n + } + }; + static X9ECParametersHolder brainpoolP224t1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve curve = new ECCurve.Fp( + //new BigInteger("2DF271E14427A346910CF7A2E6CFA7B3F484E5C2CCE1C8B730E28B3F") //Z + new BigInteger("D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF", 16), // q + new BigInteger("D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FC", 16), // a' + new BigInteger("4B337D934104CD7BEF271BF60CED1ED20DA14C08B3BB64F18A60888D", 16)); // b' + + return new X9ECParameters( + curve, + curve.decodePoint(Hex.decode("046AB1E344CE25FF3896424E7FFE14762ECB49F8928AC0C76029B4D5800374E9F5143E568CD23F3F4D7C0D4B1E41C8CC0D1C6ABD5F1A46DB4C")), // G' + new BigInteger("D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F", 16), //n + new BigInteger("01", 16)); // h + } + }; + static X9ECParametersHolder brainpoolP256r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve curve = new ECCurve.Fp( + new BigInteger("A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377", 16), // q + new BigInteger("7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9", 16), // a + new BigInteger("26DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B6", 16)); // b + + return new X9ECParameters( + curve, + curve.decodePoint(Hex.decode("048BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997")), // G + new BigInteger("A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7", 16), //n + new BigInteger("01", 16)); // h + } + }; + static X9ECParametersHolder brainpoolP256t1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve curve = new ECCurve.Fp( + //new BigInteger("3E2D4BD9597B58639AE7AA669CAB9837CF5CF20A2C852D10F655668DFC150EF0") //Z + new BigInteger("A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377", 16), // q + new BigInteger("A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5374", 16), // a' + new BigInteger("662C61C430D84EA4FE66A7733D0B76B7BF93EBC4AF2F49256AE58101FEE92B04", 16)); // b' + + return new X9ECParameters( + curve, + curve.decodePoint(Hex.decode("04A3E8EB3CC1CFE7B7732213B23A656149AFA142C47AAFBC2B79A191562E1305F42D996C823439C56D7F7B22E14644417E69BCB6DE39D027001DABE8F35B25C9BE")), // G' + new BigInteger("A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7", 16), //n + new BigInteger("01", 16)); // h + } + }; + static X9ECParametersHolder brainpoolP320r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve curve = new ECCurve.Fp( + new BigInteger("D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F92B9EC7893EC28FCD412B1F1B32E27", 16), // q + new BigInteger("3EE30B568FBAB0F883CCEBD46D3F3BB8A2A73513F5EB79DA66190EB085FFA9F492F375A97D860EB4", 16), // a + new BigInteger("520883949DFDBC42D3AD198640688A6FE13F41349554B49ACC31DCCD884539816F5EB4AC8FB1F1A6", 16)); // b + + return new X9ECParameters( + curve, + curve.decodePoint(Hex.decode("0443BD7E9AFB53D8B85289BCC48EE5BFE6F20137D10A087EB6E7871E2A10A599C710AF8D0D39E2061114FDD05545EC1CC8AB4093247F77275E0743FFED117182EAA9C77877AAAC6AC7D35245D1692E8EE1")), // G + new BigInteger("D35E472036BC4FB7E13C785ED201E065F98FCFA5B68F12A32D482EC7EE8658E98691555B44C59311", 16), //n + new BigInteger("01", 16)); // h + } + }; + static X9ECParametersHolder brainpoolP320t1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve curve = new ECCurve.Fp( + //new BigInteger("15F75CAF668077F7E85B42EB01F0A81FF56ECD6191D55CB82B7D861458A18FEFC3E5AB7496F3C7B1") //Z + new BigInteger("D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F92B9EC7893EC28FCD412B1F1B32E27", 16), // q + new BigInteger("D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F92B9EC7893EC28FCD412B1F1B32E24", 16), // a' + new BigInteger("A7F561E038EB1ED560B3D147DB782013064C19F27ED27C6780AAF77FB8A547CEB5B4FEF422340353", 16)); // b' + + return new X9ECParameters( + curve, + curve.decodePoint(Hex.decode("04925BE9FB01AFC6FB4D3E7D4990010F813408AB106C4F09CB7EE07868CC136FFF3357F624A21BED5263BA3A7A27483EBF6671DBEF7ABB30EBEE084E58A0B077AD42A5A0989D1EE71B1B9BC0455FB0D2C3")), // G' + new BigInteger("D35E472036BC4FB7E13C785ED201E065F98FCFA5B68F12A32D482EC7EE8658E98691555B44C59311", 16), //n + new BigInteger("01", 16)); // h + } + }; + static X9ECParametersHolder brainpoolP384r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve curve = new ECCurve.Fp( + new BigInteger("8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A71874700133107EC53", 16), // q + new BigInteger("7BC382C63D8C150C3C72080ACE05AFA0C2BEA28E4FB22787139165EFBA91F90F8AA5814A503AD4EB04A8C7DD22CE2826", 16), // a + new BigInteger("4A8C7DD22CE28268B39B55416F0447C2FB77DE107DCD2A62E880EA53EEB62D57CB4390295DBC9943AB78696FA504C11", 16)); // b + + return new X9ECParameters( + curve, + curve.decodePoint(Hex.decode("041D1C64F068CF45FFA2A63A81B7C13F6B8847A3E77EF14FE3DB7FCAFE0CBD10E8E826E03436D646AAEF87B2E247D4AF1E8ABE1D7520F9C2A45CB1EB8E95CFD55262B70B29FEEC5864E19C054FF99129280E4646217791811142820341263C5315")), // G + new BigInteger("8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC3103B883202E9046565", 16), //n + new BigInteger("01", 16)); // h + } + }; + static X9ECParametersHolder brainpoolP384t1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve curve = new ECCurve.Fp( + //new BigInteger("41DFE8DD399331F7166A66076734A89CD0D2BCDB7D068E44E1F378F41ECBAE97D2D63DBC87BCCDDCCC5DA39E8589291C") //Z + new BigInteger("8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A71874700133107EC53", 16), // q + new BigInteger("8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A71874700133107EC50", 16), // a' + new BigInteger("7F519EADA7BDA81BD826DBA647910F8C4B9346ED8CCDC64E4B1ABD11756DCE1D2074AA263B88805CED70355A33B471EE", 16)); // b' + + return new X9ECParameters( + curve, + curve.decodePoint(Hex.decode("0418DE98B02DB9A306F2AFCD7235F72A819B80AB12EBD653172476FECD462AABFFC4FF191B946A5F54D8D0AA2F418808CC25AB056962D30651A114AFD2755AD336747F93475B7A1FCA3B88F2B6A208CCFE469408584DC2B2912675BF5B9E582928")), // G' + new BigInteger("8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC3103B883202E9046565", 16), //n + new BigInteger("01", 16)); // h + } + }; + static X9ECParametersHolder brainpoolP512r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve curve = new ECCurve.Fp( + new BigInteger("AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3", 16), // q + new BigInteger("7830A3318B603B89E2327145AC234CC594CBDD8D3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CA", 16), // a + new BigInteger("3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CADC083E67984050B75EBAE5DD2809BD638016F723", 16)); // b + + return new X9ECParameters( + curve, + curve.decodePoint(Hex.decode("0481AEE4BDD82ED9645A21322E9C4C6A9385ED9F70B5D916C1B43B62EEF4D0098EFF3B1F78E2D0D48D50D1687B93B97D5F7C6D5047406A5E688B352209BCB9F8227DDE385D566332ECC0EABFA9CF7822FDF209F70024A57B1AA000C55B881F8111B2DCDE494A5F485E5BCA4BD88A2763AED1CA2B2FA8F0540678CD1E0F3AD80892")), // G + new BigInteger("AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA92619418661197FAC10471DB1D381085DDADDB58796829CA90069", 16), //n + new BigInteger("01", 16)); // h + } + }; + static X9ECParametersHolder brainpoolP512t1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve curve = new ECCurve.Fp( + //new BigInteger("12EE58E6764838B69782136F0F2D3BA06E27695716054092E60A80BEDB212B64E585D90BCE13761F85C3F1D2A64E3BE8FEA2220F01EBA5EEB0F35DBD29D922AB") //Z + new BigInteger("AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3", 16), // q + new BigInteger("AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F0", 16), // a' + new BigInteger("7CBBBCF9441CFAB76E1890E46884EAE321F70C0BCB4981527897504BEC3E36A62BCDFA2304976540F6450085F2DAE145C22553B465763689180EA2571867423E", 16)); // b' + + return new X9ECParameters( + curve, + curve.decodePoint(Hex.decode("04640ECE5C12788717B9C1BA06CBC2A6FEBA85842458C56DDE9DB1758D39C0313D82BA51735CDB3EA499AA77A7D6943A64F7A3F25FE26F06B51BAA2696FA9035DA5B534BD595F5AF0FA2C892376C84ACE1BB4E3019B71634C01131159CAE03CEE9D9932184BEEF216BD71DF2DADF86A627306ECFF96DBB8BACE198B61E00F8B332")), // G' + new BigInteger("AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA92619418661197FAC10471DB1D381085DDADDB58796829CA90069", 16), //n + new BigInteger("01", 16)); // h + } + }; + + static final Hashtable objIds = new Hashtable(); + static final Hashtable curves = new Hashtable(); + static final Hashtable names = new Hashtable(); + + static void defineCurve(String name, DERObjectIdentifier oid, X9ECParametersHolder holder) + { + objIds.put(name, oid); + names.put(oid, name); + curves.put(oid, holder); + } + + static + { + defineCurve("brainpoolp160r1", TeleTrusTObjectIdentifiers.brainpoolP160r1, brainpoolP160r1); + defineCurve("brainpoolp160t1", TeleTrusTObjectIdentifiers.brainpoolP160t1, brainpoolP160t1); + defineCurve("brainpoolp192r1", TeleTrusTObjectIdentifiers.brainpoolP192r1, brainpoolP192r1); + defineCurve("brainpoolp192t1", TeleTrusTObjectIdentifiers.brainpoolP192t1, brainpoolP192t1); + defineCurve("brainpoolp224r1", TeleTrusTObjectIdentifiers.brainpoolP224r1, brainpoolP224r1); + defineCurve("brainpoolp224t1", TeleTrusTObjectIdentifiers.brainpoolP224t1, brainpoolP224t1); + defineCurve("brainpoolp256r1", TeleTrusTObjectIdentifiers.brainpoolP256r1, brainpoolP256r1); + defineCurve("brainpoolp256t1", TeleTrusTObjectIdentifiers.brainpoolP256t1, brainpoolP256t1); + defineCurve("brainpoolp320r1", TeleTrusTObjectIdentifiers.brainpoolP320r1, brainpoolP320r1); + defineCurve("brainpoolp320t1", TeleTrusTObjectIdentifiers.brainpoolP320t1, brainpoolP320t1); + defineCurve("brainpoolp384r1", TeleTrusTObjectIdentifiers.brainpoolP384r1, brainpoolP384r1); + defineCurve("brainpoolp384t1", TeleTrusTObjectIdentifiers.brainpoolP384t1, brainpoolP384t1); + defineCurve("brainpoolp512r1", TeleTrusTObjectIdentifiers.brainpoolP512r1, brainpoolP512r1); + defineCurve("brainpoolp512t1", TeleTrusTObjectIdentifiers.brainpoolP512t1, brainpoolP512t1); + } + + public static X9ECParameters getByName( + String name) + { + DERObjectIdentifier oid = (DERObjectIdentifier)objIds.get(Strings.toLowerCase(name)); + + if (oid != null) + { + return getByOID(oid); + } + + return null; + } + + /** + * return the X9ECParameters object for the named curve represented by + * the passed in object identifier. Null if the curve isn't present. + * + * @param oid an object identifier representing a named curve, if present. + */ + public static X9ECParameters getByOID( + DERObjectIdentifier oid) + { + X9ECParametersHolder holder = (X9ECParametersHolder)curves.get(oid); + + if (holder != null) + { + return holder.getParameters(); + } + + return null; + } + + /** + * return the object identifier signified by the passed in name. Null + * if there is no object identifier associated with name. + * + * @return the object identifier associated with name, if present. + */ + public static DERObjectIdentifier getOID( + String name) + { + return (DERObjectIdentifier)objIds.get(Strings.toLowerCase(name)); + } + + /** + * return the named curve name represented by the given object identifier. + */ + public static String getName( + DERObjectIdentifier oid) + { + return (String)names.get(oid); + } + + /** + * returns an enumeration containing the name strings for curves + * contained in this structure. + */ + public static Enumeration getNames() + { + return objIds.keys(); + } + + public static DERObjectIdentifier getOID(short curvesize, boolean twisted) + { + return getOID("brainpoolP" + curvesize + (twisted ? "t" : "r") + "1"); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/teletrust/TeleTrusTObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/teletrust/TeleTrusTObjectIdentifiers.java new file mode 100644 index 000000000..4f3af0e11 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/teletrust/TeleTrusTObjectIdentifiers.java @@ -0,0 +1,42 @@ +package com.google.bitcoin.bouncycastle.asn1.teletrust; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface TeleTrusTObjectIdentifiers +{ + static final String teleTrusTAlgorithm = "1.3.36.3"; + + static final DERObjectIdentifier ripemd160 = new DERObjectIdentifier(teleTrusTAlgorithm + ".2.1"); + static final DERObjectIdentifier ripemd128 = new DERObjectIdentifier(teleTrusTAlgorithm + ".2.2"); + static final DERObjectIdentifier ripemd256 = new DERObjectIdentifier(teleTrusTAlgorithm + ".2.3"); + + static final String teleTrusTRSAsignatureAlgorithm = teleTrusTAlgorithm + ".3.1"; + + static final DERObjectIdentifier rsaSignatureWithripemd160 = new DERObjectIdentifier(teleTrusTRSAsignatureAlgorithm + ".2"); + static final DERObjectIdentifier rsaSignatureWithripemd128 = new DERObjectIdentifier(teleTrusTRSAsignatureAlgorithm + ".3"); + static final DERObjectIdentifier rsaSignatureWithripemd256 = new DERObjectIdentifier(teleTrusTRSAsignatureAlgorithm + ".4"); + + static final DERObjectIdentifier ecSign = new DERObjectIdentifier(teleTrusTAlgorithm + ".3.2"); + + static final DERObjectIdentifier ecSignWithSha1 = new DERObjectIdentifier(ecSign + ".1"); + static final DERObjectIdentifier ecSignWithRipemd160 = new DERObjectIdentifier(ecSign + ".2"); + + static final DERObjectIdentifier ecc_brainpool = new DERObjectIdentifier(teleTrusTAlgorithm + ".3.2.8"); + static final DERObjectIdentifier ellipticCurve = new DERObjectIdentifier(ecc_brainpool + ".1"); + static final DERObjectIdentifier versionOne = new DERObjectIdentifier(ellipticCurve + ".1"); + + static final DERObjectIdentifier brainpoolP160r1 = new DERObjectIdentifier(versionOne + ".1"); + static final DERObjectIdentifier brainpoolP160t1 = new DERObjectIdentifier(versionOne + ".2"); + static final DERObjectIdentifier brainpoolP192r1 = new DERObjectIdentifier(versionOne + ".3"); + static final DERObjectIdentifier brainpoolP192t1 = new DERObjectIdentifier(versionOne + ".4"); + static final DERObjectIdentifier brainpoolP224r1 = new DERObjectIdentifier(versionOne + ".5"); + static final DERObjectIdentifier brainpoolP224t1 = new DERObjectIdentifier(versionOne + ".6"); + static final DERObjectIdentifier brainpoolP256r1 = new DERObjectIdentifier(versionOne + ".7"); + static final DERObjectIdentifier brainpoolP256t1 = new DERObjectIdentifier(versionOne + ".8"); + static final DERObjectIdentifier brainpoolP320r1 = new DERObjectIdentifier(versionOne + ".9"); + static final DERObjectIdentifier brainpoolP320t1 = new DERObjectIdentifier(versionOne+".10"); + static final DERObjectIdentifier brainpoolP384r1 = new DERObjectIdentifier(versionOne+".11"); + static final DERObjectIdentifier brainpoolP384t1 = new DERObjectIdentifier(versionOne+".12"); + static final DERObjectIdentifier brainpoolP512r1 = new DERObjectIdentifier(versionOne+".13"); + static final DERObjectIdentifier brainpoolP512t1 = new DERObjectIdentifier(versionOne+".14"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/tsp/Accuracy.java b/src/com/google/bitcoin/bouncycastle/asn1/tsp/Accuracy.java new file mode 100644 index 000000000..d56e10385 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/tsp/Accuracy.java @@ -0,0 +1,174 @@ +package com.google.bitcoin.bouncycastle.asn1.tsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + + +public class Accuracy + extends ASN1Encodable +{ + DERInteger seconds; + + DERInteger millis; + + DERInteger micros; + + // constantes + protected static final int MIN_MILLIS = 1; + + protected static final int MAX_MILLIS = 999; + + protected static final int MIN_MICROS = 1; + + protected static final int MAX_MICROS = 999; + + protected Accuracy() + { + } + + public Accuracy( + DERInteger seconds, + DERInteger millis, + DERInteger micros) + { + this.seconds = seconds; + + //Verifications + if (millis != null + && (millis.getValue().intValue() < MIN_MILLIS || millis + .getValue().intValue() > MAX_MILLIS)) + { + throw new IllegalArgumentException( + "Invalid millis field : not in (1..999)"); + } + else + { + this.millis = millis; + } + + if (micros != null + && (micros.getValue().intValue() < MIN_MICROS || micros + .getValue().intValue() > MAX_MICROS)) + { + throw new IllegalArgumentException( + "Invalid micros field : not in (1..999)"); + } + else + { + this.micros = micros; + } + + } + + public Accuracy(ASN1Sequence seq) + { + seconds = null; + millis = null; + micros = null; + + for (int i = 0; i < seq.size(); i++) + { + // seconds + if (seq.getObjectAt(i) instanceof DERInteger) + { + seconds = (DERInteger) seq.getObjectAt(i); + } + else if (seq.getObjectAt(i) instanceof DERTaggedObject) + { + DERTaggedObject extra = (DERTaggedObject) seq.getObjectAt(i); + + switch (extra.getTagNo()) + { + case 0: + millis = DERInteger.getInstance(extra, false); + if (millis.getValue().intValue() < MIN_MILLIS + || millis.getValue().intValue() > MAX_MILLIS) + { + throw new IllegalArgumentException( + "Invalid millis field : not in (1..999)."); + } + break; + case 1: + micros = DERInteger.getInstance(extra, false); + if (micros.getValue().intValue() < MIN_MICROS + || micros.getValue().intValue() > MAX_MICROS) + { + throw new IllegalArgumentException( + "Invalid micros field : not in (1..999)."); + } + break; + default: + throw new IllegalArgumentException("Invalig tag number"); + } + } + } + } + + public static Accuracy getInstance(Object o) + { + if (o == null || o instanceof Accuracy) + { + return (Accuracy) o; + } + else if (o instanceof ASN1Sequence) + { + return new Accuracy((ASN1Sequence) o); + } + + throw new IllegalArgumentException( + "Unknown object in 'Accuracy' factory : " + + o.getClass().getName() + "."); + } + + public DERInteger getSeconds() + { + return seconds; + } + + public DERInteger getMillis() + { + return millis; + } + + public DERInteger getMicros() + { + return micros; + } + + /** + *
+     * Accuracy ::= SEQUENCE {
+     *             seconds        INTEGER              OPTIONAL,
+     *             millis     [0] INTEGER  (1..999)    OPTIONAL,
+     *             micros     [1] INTEGER  (1..999)    OPTIONAL
+     *             }
+     * 
+ */ + public DERObject toASN1Object() + { + + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (seconds != null) + { + v.add(seconds); + } + + if (millis != null) + { + v.add(new DERTaggedObject(false, 0, millis)); + } + + if (micros != null) + { + v.add(new DERTaggedObject(false, 1, micros)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/tsp/MessageImprint.java b/src/com/google/bitcoin/bouncycastle/asn1/tsp/MessageImprint.java new file mode 100644 index 000000000..9723b35b7 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/tsp/MessageImprint.java @@ -0,0 +1,77 @@ +package com.google.bitcoin.bouncycastle.asn1.tsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; + +public class MessageImprint + extends ASN1Encodable +{ + AlgorithmIdentifier hashAlgorithm; + byte[] hashedMessage; + + /** + * @param o + * @return a MessageImprint object. + */ + public static MessageImprint getInstance(Object o) + { + if (o == null || o instanceof MessageImprint) + { + return (MessageImprint)o; + } + else if (o instanceof ASN1Sequence) + { + return new MessageImprint((ASN1Sequence)o); + } + + throw new IllegalArgumentException("Bad object in factory."); + } + + public MessageImprint( + ASN1Sequence seq) + { + this.hashAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(0)); + this.hashedMessage = ASN1OctetString.getInstance(seq.getObjectAt(1)).getOctets(); + } + + public MessageImprint( + AlgorithmIdentifier hashAlgorithm, + byte[] hashedMessage) + { + this.hashAlgorithm = hashAlgorithm; + this.hashedMessage = hashedMessage; + } + + public AlgorithmIdentifier getHashAlgorithm() + { + return hashAlgorithm; + } + + public byte[] getHashedMessage() + { + return hashedMessage; + } + + /** + *
+     *    MessageImprint ::= SEQUENCE  {
+     *       hashAlgorithm                AlgorithmIdentifier,
+     *       hashedMessage                OCTET STRING  }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(hashAlgorithm); + v.add(new DEROctetString(hashedMessage)); + + return new DERSequence(v); + } +} \ No newline at end of file diff --git a/src/com/google/bitcoin/bouncycastle/asn1/tsp/TSTInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/tsp/TSTInfo.java new file mode 100644 index 000000000..51699edab --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/tsp/TSTInfo.java @@ -0,0 +1,254 @@ +package com.google.bitcoin.bouncycastle.asn1.tsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1InputStream; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBoolean; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.GeneralName; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Extensions; + +import java.io.IOException; +import java.util.Enumeration; + +public class TSTInfo + extends ASN1Encodable +{ + DERInteger version; + + DERObjectIdentifier tsaPolicyId; + + MessageImprint messageImprint; + + DERInteger serialNumber; + + DERGeneralizedTime genTime; + + Accuracy accuracy; + + DERBoolean ordering; + + DERInteger nonce; + + GeneralName tsa; + + X509Extensions extensions; + + public static TSTInfo getInstance(Object o) + { + if (o == null || o instanceof TSTInfo) + { + return (TSTInfo) o; + } + else if (o instanceof ASN1Sequence) + { + return new TSTInfo((ASN1Sequence) o); + } + else if (o instanceof ASN1OctetString) + { + try + { + return getInstance(new ASN1InputStream(((ASN1OctetString)o).getOctets()).readObject()); + } + catch (IOException ioEx) + { + throw new IllegalArgumentException( + "Bad object format in 'TSTInfo' factory."); + } + } + + throw new IllegalArgumentException( + "Unknown object in 'TSTInfo' factory : " + + o.getClass().getName() + "."); + } + + public TSTInfo(ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + // version + version = DERInteger.getInstance(e.nextElement()); + + // tsaPolicy + tsaPolicyId = DERObjectIdentifier.getInstance(e.nextElement()); + + // messageImprint + messageImprint = MessageImprint.getInstance(e.nextElement()); + + // serialNumber + serialNumber = DERInteger.getInstance(e.nextElement()); + + // genTime + genTime = DERGeneralizedTime.getInstance(e.nextElement()); + + // default for ordering + ordering = new DERBoolean(false); + + while (e.hasMoreElements()) + { + DERObject o = (DERObject) e.nextElement(); + + if (o instanceof ASN1TaggedObject) + { + DERTaggedObject tagged = (DERTaggedObject) o; + + switch (tagged.getTagNo()) + { + case 0: + tsa = GeneralName.getInstance(tagged, true); + break; + case 1: + extensions = X509Extensions.getInstance(tagged, false); + break; + default: + throw new IllegalArgumentException("Unknown tag value " + tagged.getTagNo()); + } + } + else if (o instanceof DERSequence) + { + accuracy = Accuracy.getInstance(o); + } + else if (o instanceof DERBoolean) + { + ordering = DERBoolean.getInstance(o); + } + else if (o instanceof DERInteger) + { + nonce = DERInteger.getInstance(o); + } + + } + } + + public TSTInfo(DERObjectIdentifier tsaPolicyId, MessageImprint messageImprint, + DERInteger serialNumber, DERGeneralizedTime genTime, + Accuracy accuracy, DERBoolean ordering, DERInteger nonce, + GeneralName tsa, X509Extensions extensions) + { + version = new DERInteger(1); + this.tsaPolicyId = tsaPolicyId; + this.messageImprint = messageImprint; + this.serialNumber = serialNumber; + this.genTime = genTime; + + this.accuracy = accuracy; + this.ordering = ordering; + this.nonce = nonce; + this.tsa = tsa; + this.extensions = extensions; + } + + public MessageImprint getMessageImprint() + { + return messageImprint; + } + + public DERObjectIdentifier getPolicy() + { + return tsaPolicyId; + } + + public DERInteger getSerialNumber() + { + return serialNumber; + } + + public Accuracy getAccuracy() + { + return accuracy; + } + + public DERGeneralizedTime getGenTime() + { + return genTime; + } + + public DERBoolean getOrdering() + { + return ordering; + } + + public DERInteger getNonce() + { + return nonce; + } + + public GeneralName getTsa() + { + return tsa; + } + + public X509Extensions getExtensions() + { + return extensions; + } + + /** + *
+     * 
+     *     TSTInfo ::= SEQUENCE  {
+     *        version                      INTEGER  { v1(1) },
+     *        policy                       TSAPolicyId,
+     *        messageImprint               MessageImprint,
+     *          -- MUST have the same value as the similar field in
+     *          -- TimeStampReq
+     *        serialNumber                 INTEGER,
+     *         -- Time-Stamping users MUST be ready to accommodate integers
+     *         -- up to 160 bits.
+     *        genTime                      GeneralizedTime,
+     *        accuracy                     Accuracy                 OPTIONAL,
+     *        ordering                     BOOLEAN             DEFAULT FALSE,
+     *        nonce                        INTEGER                  OPTIONAL,
+     *          -- MUST be present if the similar field was present
+     *          -- in TimeStampReq.  In that case it MUST have the same value.
+     *        tsa                          [0] GeneralName          OPTIONAL,
+     *        extensions                   [1] IMPLICIT Extensions   OPTIONAL  }
+     * 
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector seq = new ASN1EncodableVector(); + seq.add(version); + + seq.add(tsaPolicyId); + seq.add(messageImprint); + seq.add(serialNumber); + seq.add(genTime); + + if (accuracy != null) + { + seq.add(accuracy); + } + + if (ordering != null && ordering.isTrue()) + { + seq.add(ordering); + } + + if (nonce != null) + { + seq.add(nonce); + } + + if (tsa != null) + { + seq.add(new DERTaggedObject(true, 0, tsa)); + } + + if (extensions != null) + { + seq.add(new DERTaggedObject(false, 1, extensions)); + } + + return new DERSequence(seq); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/tsp/TimeStampReq.java b/src/com/google/bitcoin/bouncycastle/asn1/tsp/TimeStampReq.java new file mode 100644 index 000000000..dc545495d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/tsp/TimeStampReq.java @@ -0,0 +1,181 @@ +package com.google.bitcoin.bouncycastle.asn1.tsp; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBoolean; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Extensions; + +public class TimeStampReq + extends ASN1Encodable +{ + DERInteger version; + + MessageImprint messageImprint; + + DERObjectIdentifier tsaPolicy; + + DERInteger nonce; + + DERBoolean certReq; + + X509Extensions extensions; + + public static TimeStampReq getInstance(Object o) + { + if (o == null || o instanceof TimeStampReq) + { + return (TimeStampReq) o; + } + else if (o instanceof ASN1Sequence) + { + return new TimeStampReq((ASN1Sequence) o); + } + + throw new IllegalArgumentException( + "Unknown object in 'TimeStampReq' factory : " + + o.getClass().getName() + "."); + } + + public TimeStampReq(ASN1Sequence seq) + { + int nbObjects = seq.size(); + + int seqStart = 0; + + // version + version = DERInteger.getInstance(seq.getObjectAt(seqStart)); + + seqStart++; + + // messageImprint + messageImprint = MessageImprint.getInstance(seq.getObjectAt(seqStart)); + + seqStart++; + + for (int opt = seqStart; opt < nbObjects; opt++) + { + // tsaPolicy + if (seq.getObjectAt(opt) instanceof DERObjectIdentifier) + { + tsaPolicy = DERObjectIdentifier.getInstance(seq.getObjectAt(opt)); + } + // nonce + else if (seq.getObjectAt(opt) instanceof DERInteger) + { + nonce = DERInteger.getInstance(seq.getObjectAt(opt)); + } + // certReq + else if (seq.getObjectAt(opt) instanceof DERBoolean) + { + certReq = DERBoolean.getInstance(seq.getObjectAt(opt)); + } + // extensions + else if (seq.getObjectAt(opt) instanceof ASN1TaggedObject) + { + ASN1TaggedObject tagged = (ASN1TaggedObject)seq.getObjectAt(opt); + if (tagged.getTagNo() == 0) + { + extensions = X509Extensions.getInstance(tagged, false); + } + } + } + } + + public TimeStampReq( + MessageImprint messageImprint, + DERObjectIdentifier tsaPolicy, + DERInteger nonce, + DERBoolean certReq, + X509Extensions extensions) + { + // default + version = new DERInteger(1); + + this.messageImprint = messageImprint; + this.tsaPolicy = tsaPolicy; + this.nonce = nonce; + this.certReq = certReq; + this.extensions = extensions; + } + + public DERInteger getVersion() + { + return version; + } + + public MessageImprint getMessageImprint() + { + return messageImprint; + } + + public DERObjectIdentifier getReqPolicy() + { + return tsaPolicy; + } + + public DERInteger getNonce() + { + return nonce; + } + + public DERBoolean getCertReq() + { + return certReq; + } + + public X509Extensions getExtensions() + { + return extensions; + } + + /** + *
+     * TimeStampReq ::= SEQUENCE  {
+     *  version                      INTEGER  { v1(1) },
+     *  messageImprint               MessageImprint,
+     *    --a hash algorithm OID and the hash value of the data to be
+     *    --time-stamped
+     *  reqPolicy             TSAPolicyId              OPTIONAL,
+     *  nonce                 INTEGER                  OPTIONAL,
+     *  certReq               BOOLEAN                  DEFAULT FALSE,
+     *  extensions            [0] IMPLICIT Extensions  OPTIONAL
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(messageImprint); + + if (tsaPolicy != null) + { + v.add(tsaPolicy); + } + + if (nonce != null) + { + v.add(nonce); + } + + if (certReq != null && certReq.isTrue()) + { + v.add(certReq); + } + + if (extensions != null) + { + v.add(new DERTaggedObject(false, 0, extensions)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/tsp/TimeStampResp.java b/src/com/google/bitcoin/bouncycastle/asn1/tsp/TimeStampResp.java new file mode 100644 index 000000000..75dc2cc42 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/tsp/TimeStampResp.java @@ -0,0 +1,86 @@ +package com.google.bitcoin.bouncycastle.asn1.tsp; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.cmp.PKIStatusInfo; +import com.google.bitcoin.bouncycastle.asn1.cms.ContentInfo; + + +public class TimeStampResp + extends ASN1Encodable +{ + PKIStatusInfo pkiStatusInfo; + + ContentInfo timeStampToken; + + public static TimeStampResp getInstance(Object o) + { + if (o == null || o instanceof TimeStampResp) + { + return (TimeStampResp) o; + } + else if (o instanceof ASN1Sequence) + { + return new TimeStampResp((ASN1Sequence) o); + } + + throw new IllegalArgumentException( + "unknown object in 'TimeStampResp' factory : " + + o.getClass().getName() + "."); + } + + public TimeStampResp(ASN1Sequence seq) + { + + Enumeration e = seq.getObjects(); + + // status + pkiStatusInfo = PKIStatusInfo.getInstance(e.nextElement()); + + if (e.hasMoreElements()) + { + timeStampToken = ContentInfo.getInstance(e.nextElement()); + } + } + + public TimeStampResp(PKIStatusInfo pkiStatusInfo, ContentInfo timeStampToken) + { + this.pkiStatusInfo = pkiStatusInfo; + this.timeStampToken = timeStampToken; + } + + public PKIStatusInfo getStatus() + { + return pkiStatusInfo; + } + + public ContentInfo getTimeStampToken() + { + return timeStampToken; + } + + /** + *
+     * TimeStampResp ::= SEQUENCE  {
+     *   status                  PKIStatusInfo,
+     *   timeStampToken          TimeStampToken     OPTIONAL  }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(pkiStatusInfo); + if (timeStampToken != null) + { + v.add(timeStampToken); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/util/ASN1Dump.java b/src/com/google/bitcoin/bouncycastle/asn1/util/ASN1Dump.java new file mode 100644 index 000000000..d4875588f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/util/ASN1Dump.java @@ -0,0 +1,473 @@ +package com.google.bitcoin.bouncycastle.asn1.util; + +import java.io.IOException; +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.BERApplicationSpecific; +import com.google.bitcoin.bouncycastle.asn1.BERConstructedOctetString; +import com.google.bitcoin.bouncycastle.asn1.BERConstructedSequence; +import com.google.bitcoin.bouncycastle.asn1.BERSequence; +import com.google.bitcoin.bouncycastle.asn1.BERSet; +import com.google.bitcoin.bouncycastle.asn1.BERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERApplicationSpecific; +import com.google.bitcoin.bouncycastle.asn1.DERBMPString; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERBoolean; +import com.google.bitcoin.bouncycastle.asn1.DERConstructedSequence; +import com.google.bitcoin.bouncycastle.asn1.DERConstructedSet; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DEREnumerated; +import com.google.bitcoin.bouncycastle.asn1.DERExternal; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERIA5String; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERNull; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERPrintableString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERSet; +import com.google.bitcoin.bouncycastle.asn1.DERT61String; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERTags; +import com.google.bitcoin.bouncycastle.asn1.DERUTCTime; +import com.google.bitcoin.bouncycastle.asn1.DERUTF8String; +import com.google.bitcoin.bouncycastle.asn1.DERUnknownTag; +import com.google.bitcoin.bouncycastle.asn1.DERVisibleString; +import com.google.bitcoin.bouncycastle.util.encoders.Hex; + +@SuppressWarnings("deprecation") +public class ASN1Dump +{ + private static final String TAB = " "; + private static final int SAMPLE_SIZE = 32; + + /** + * dump a DER object as a formatted string with indentation + * + * @param obj the DERObject to be dumped out. + */ + static void _dumpAsString( + String indent, + boolean verbose, + DERObject obj, + StringBuffer buf) + { + String nl = System.getProperty("line.separator"); + if (obj instanceof ASN1Sequence) + { + Enumeration e = ((ASN1Sequence)obj).getObjects(); + String tab = indent + TAB; + + buf.append(indent); + if (obj instanceof BERConstructedSequence) + { + buf.append("BER ConstructedSequence"); + } + else if (obj instanceof DERConstructedSequence) + { + buf.append("DER ConstructedSequence"); + } + else if (obj instanceof BERSequence) + { + buf.append("BER Sequence"); + } + else if (obj instanceof DERSequence) + { + buf.append("DER Sequence"); + } + else + { + buf.append("Sequence"); + } + + buf.append(nl); + + while (e.hasMoreElements()) + { + Object o = e.nextElement(); + + if (o == null || o.equals(new DERNull())) + { + buf.append(tab); + buf.append("NULL"); + buf.append(nl); + } + else if (o instanceof DERObject) + { + _dumpAsString(tab, verbose, (DERObject)o, buf); + } + else + { + _dumpAsString(tab, verbose, ((DEREncodable)o).getDERObject(), buf); + } + } + } + else if (obj instanceof DERTaggedObject) + { + String tab = indent + TAB; + + buf.append(indent); + if (obj instanceof BERTaggedObject) + { + buf.append("BER Tagged ["); + } + else + { + buf.append("Tagged ["); + } + + DERTaggedObject o = (DERTaggedObject)obj; + + buf.append(Integer.toString(o.getTagNo())); + buf.append(']'); + + if (!o.isExplicit()) + { + buf.append(" IMPLICIT "); + } + + buf.append(nl); + + if (o.isEmpty()) + { + buf.append(tab); + buf.append("EMPTY"); + buf.append(nl); + } + else + { + _dumpAsString(tab, verbose, o.getObject(), buf); + } + } + else if (obj instanceof DERConstructedSet) + { + Enumeration e = ((ASN1Set)obj).getObjects(); + String tab = indent + TAB; + + buf.append(indent); + buf.append("ConstructedSet"); + buf.append(nl); + + while (e.hasMoreElements()) + { + Object o = e.nextElement(); + + if (o == null) + { + buf.append(tab); + buf.append("NULL"); + buf.append(nl); + } + else if (o instanceof DERObject) + { + _dumpAsString(tab, verbose, (DERObject)o, buf); + } + else + { + _dumpAsString(tab, verbose, ((DEREncodable)o).getDERObject(), buf); + } + } + } + else if (obj instanceof BERSet) + { + Enumeration e = ((ASN1Set)obj).getObjects(); + String tab = indent + TAB; + + buf.append(indent); + buf.append("BER Set"); + buf.append(nl); + + while (e.hasMoreElements()) + { + Object o = e.nextElement(); + + if (o == null) + { + buf.append(tab); + buf.append("NULL"); + buf.append(nl); + } + else if (o instanceof DERObject) + { + _dumpAsString(tab, verbose, (DERObject)o, buf); + } + else + { + _dumpAsString(tab, verbose, ((DEREncodable)o).getDERObject(), buf); + } + } + } + else if (obj instanceof DERSet) + { + Enumeration e = ((ASN1Set)obj).getObjects(); + String tab = indent + TAB; + + buf.append(indent); + buf.append("DER Set"); + buf.append(nl); + + while (e.hasMoreElements()) + { + Object o = e.nextElement(); + + if (o == null) + { + buf.append(tab); + buf.append("NULL"); + buf.append(nl); + } + else if (o instanceof DERObject) + { + _dumpAsString(tab, verbose, (DERObject)o, buf); + } + else + { + _dumpAsString(tab, verbose, ((DEREncodable)o).getDERObject(), buf); + } + } + } + else if (obj instanceof DERObjectIdentifier) + { + buf.append(indent + "ObjectIdentifier(" + ((DERObjectIdentifier)obj).getId() + ")" + nl); + } + else if (obj instanceof DERBoolean) + { + buf.append(indent + "Boolean(" + ((DERBoolean)obj).isTrue() + ")" + nl); + } + else if (obj instanceof DERInteger) + { + buf.append(indent + "Integer(" + ((DERInteger)obj).getValue() + ")" + nl); + } + else if (obj instanceof BERConstructedOctetString) + { + ASN1OctetString oct = (ASN1OctetString)obj; + buf.append(indent + "BER Constructed Octet String" + "[" + oct.getOctets().length + "] "); + if (verbose) + { + buf.append(dumpBinaryDataAsString(indent, oct.getOctets())); + } + else{ + buf.append(nl); + } + } + else if (obj instanceof DEROctetString) + { + ASN1OctetString oct = (ASN1OctetString)obj; + buf.append(indent + "DER Octet String" + "[" + oct.getOctets().length + "] "); + if (verbose) + { + buf.append(dumpBinaryDataAsString(indent, oct.getOctets())); + } + else{ + buf.append(nl); + } + } + else if (obj instanceof DERBitString) + { + DERBitString bt = (DERBitString)obj; + buf.append(indent + "DER Bit String" + "[" + bt.getBytes().length + ", " + bt.getPadBits() + "] "); + if (verbose) + { + buf.append(dumpBinaryDataAsString(indent, bt.getBytes())); + } + else{ + buf.append(nl); + } + } + else if (obj instanceof DERIA5String) + { + buf.append(indent + "IA5String(" + ((DERIA5String)obj).getString() + ") " + nl); + } + else if (obj instanceof DERUTF8String) + { + buf.append(indent + "UTF8String(" + ((DERUTF8String)obj).getString() + ") " + nl); + } + else if (obj instanceof DERPrintableString) + { + buf.append(indent + "PrintableString(" + ((DERPrintableString)obj).getString() + ") " + nl); + } + else if (obj instanceof DERVisibleString) + { + buf.append(indent + "VisibleString(" + ((DERVisibleString)obj).getString() + ") " + nl); + } + else if (obj instanceof DERBMPString) + { + buf.append(indent + "BMPString(" + ((DERBMPString)obj).getString() + ") " + nl); + } + else if (obj instanceof DERT61String) + { + buf.append(indent + "T61String(" + ((DERT61String)obj).getString() + ") " + nl); + } + else if (obj instanceof DERUTCTime) + { + buf.append(indent + "UTCTime(" + ((DERUTCTime)obj).getTime() + ") " + nl); + } + else if (obj instanceof DERGeneralizedTime) + { + buf.append(indent + "GeneralizedTime(" + ((DERGeneralizedTime)obj).getTime() + ") " + nl); + } + else if (obj instanceof DERUnknownTag) + { + buf.append(indent + "Unknown " + Integer.toString(((DERUnknownTag)obj).getTag(), 16) + " " + new String(Hex.encode(((DERUnknownTag)obj).getData())) + nl); + } + else if (obj instanceof BERApplicationSpecific) + { + buf.append(outputApplicationSpecific("BER", indent, verbose, obj, nl)); + } + else if (obj instanceof DERApplicationSpecific) + { + buf.append(outputApplicationSpecific("DER", indent, verbose, obj, nl)); + } + else if (obj instanceof DEREnumerated) + { + DEREnumerated en = (DEREnumerated) obj; + buf.append(indent + "DER Enumerated(" + en.getValue() + ")" + nl); + } + else if (obj instanceof DERExternal) + { + DERExternal ext = (DERExternal) obj; + buf.append(indent + "External " + nl); + String tab = indent + TAB; + if (ext.getDirectReference() != null) + { + buf.append(tab + "Direct Reference: " + ext.getDirectReference().getId() + nl); + } + if (ext.getIndirectReference() != null) + { + buf.append(tab + "Indirect Reference: " + ext.getIndirectReference().toString() + nl); + } + if (ext.getDataValueDescriptor() != null) + { + _dumpAsString(tab, verbose, ext.getDataValueDescriptor(), buf); + } + buf.append(tab + "Encoding: " + ext.getEncoding() + nl); + _dumpAsString(tab, verbose, ext.getExternalContent(), buf); + } + else + { + buf.append(indent + obj.toString() + nl); + } + } + + private static String outputApplicationSpecific(String type, String indent, boolean verbose, DERObject obj, String nl) + { + DERApplicationSpecific app = (DERApplicationSpecific)obj; + StringBuffer buf = new StringBuffer(); + + if (app.isConstructed()) + { + try + { + ASN1Sequence s = ASN1Sequence.getInstance(app.getObject(DERTags.SEQUENCE)); + buf.append(indent + type + " ApplicationSpecific[" + app.getApplicationTag() + "]" + nl); + for (Enumeration e = s.getObjects(); e.hasMoreElements();) + { + _dumpAsString(indent + TAB, verbose, (DERObject)e.nextElement(), buf); + } + } + catch (IOException e) + { + buf.append(e); + } + return buf.toString(); + } + + return indent + type + " ApplicationSpecific[" + app.getApplicationTag() + "] (" + new String(Hex.encode(app.getContents())) + ")" + nl; + } + + /** + * dump out a DER object as a formatted string, in non-verbose mode. + * + * @param obj the DERObject to be dumped out. + * @return the resulting string. + */ + public static String dumpAsString( + Object obj) + { + return dumpAsString(obj, false); + } + + /** + * Dump out the object as a string. + * + * @param obj the object to be dumped + * @param verbose if true, dump out the contents of octet and bit strings. + * @return the resulting string. + */ + public static String dumpAsString( + Object obj, + boolean verbose) + { + StringBuffer buf = new StringBuffer(); + + if (obj instanceof DERObject) + { + _dumpAsString("", verbose, (DERObject)obj, buf); + } + else if (obj instanceof DEREncodable) + { + _dumpAsString("", verbose, ((DEREncodable)obj).getDERObject(), buf); + } + else + { + return "unknown object type " + obj.toString(); + } + + return buf.toString(); + } + + private static String dumpBinaryDataAsString(String indent, byte[] bytes) + { + String nl = System.getProperty("line.separator"); + StringBuffer buf = new StringBuffer(); + + indent += TAB; + + buf.append(nl); + for (int i = 0; i < bytes.length; i += SAMPLE_SIZE) + { + if (bytes.length - i > SAMPLE_SIZE) + { + buf.append(indent); + buf.append(new String(Hex.encode(bytes, i, SAMPLE_SIZE))); + buf.append(TAB); + buf.append(calculateAscString(bytes, i, SAMPLE_SIZE)); + buf.append(nl); + } + else + { + buf.append(indent); + buf.append(new String(Hex.encode(bytes, i, bytes.length - i))); + for (int j = bytes.length - i; j != SAMPLE_SIZE; j++) + { + buf.append(" "); + } + buf.append(TAB); + buf.append(calculateAscString(bytes, i, bytes.length - i)); + buf.append(nl); + } + } + + return buf.toString(); + } + + private static String calculateAscString(byte[] bytes, int off, int len) + { + StringBuffer buf = new StringBuffer(); + + for (int i = off; i != off + len; i++) + { + if (bytes[i] >= ' ' && bytes[i] <= '~') + { + buf.append((char)bytes[i]); + } + } + + return buf.toString(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/util/DERDump.java b/src/com/google/bitcoin/bouncycastle/asn1/util/DERDump.java new file mode 100644 index 000000000..d05946244 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/util/DERDump.java @@ -0,0 +1,41 @@ +package com.google.bitcoin.bouncycastle.asn1.util; + +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; + +/** + * @deprecated use ASN1Dump. + */ +public class DERDump + extends ASN1Dump +{ + /** + * dump out a DER object as a formatted string + * + * @param obj the DERObject to be dumped out. + */ + public static String dumpAsString( + DERObject obj) + { + StringBuffer buf = new StringBuffer(); + + _dumpAsString("", false, obj, buf); + + return buf.toString(); + } + + /** + * dump out a DER object as a formatted string + * + * @param obj the DERObject to be dumped out. + */ + public static String dumpAsString( + DEREncodable obj) + { + StringBuffer buf = new StringBuffer(); + + _dumpAsString("", false, obj.getDERObject(), buf); + + return buf.toString(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/util/Dump.java b/src/com/google/bitcoin/bouncycastle/asn1/util/Dump.java new file mode 100644 index 000000000..4b913683f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/util/Dump.java @@ -0,0 +1,22 @@ +package com.google.bitcoin.bouncycastle.asn1.util; + +import java.io.FileInputStream; + +import com.google.bitcoin.bouncycastle.asn1.ASN1InputStream; + +public class Dump +{ + public static void main( + String args[]) + throws Exception + { + FileInputStream fIn = new FileInputStream(args[0]); + ASN1InputStream bIn = new ASN1InputStream(fIn); + Object obj = null; + + while ((obj = bIn.readObject()) != null) + { + System.out.println(ASN1Dump.dumpAsString(obj)); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x500/DirectoryString.java b/src/com/google/bitcoin/bouncycastle/asn1/x500/DirectoryString.java new file mode 100644 index 000000000..32933e309 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x500/DirectoryString.java @@ -0,0 +1,125 @@ +package com.google.bitcoin.bouncycastle.asn1.x500; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBMPString; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERPrintableString; +import com.google.bitcoin.bouncycastle.asn1.DERString; +import com.google.bitcoin.bouncycastle.asn1.DERT61String; +import com.google.bitcoin.bouncycastle.asn1.DERUTF8String; +import com.google.bitcoin.bouncycastle.asn1.DERUniversalString; + +public class DirectoryString + extends ASN1Encodable + implements ASN1Choice, DERString +{ + private DERString string; + + public static DirectoryString getInstance(Object o) + { + if (o instanceof DirectoryString) + { + return (DirectoryString)o; + } + + if (o instanceof DERT61String) + { + return new DirectoryString((DERT61String)o); + } + + if (o instanceof DERPrintableString) + { + return new DirectoryString((DERPrintableString)o); + } + + if (o instanceof DERUniversalString) + { + return new DirectoryString((DERUniversalString)o); + } + + if (o instanceof DERUTF8String) + { + return new DirectoryString((DERUTF8String)o); + } + + if (o instanceof DERBMPString) + { + return new DirectoryString((DERBMPString)o); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + o.getClass().getName()); + } + + public static DirectoryString getInstance(ASN1TaggedObject o, boolean explicit) + { + if (!explicit) + { + throw new IllegalArgumentException("choice item must be explicitly tagged"); + } + + return getInstance(o.getObject()); + } + + private DirectoryString( + DERT61String string) + { + this.string = string; + } + + private DirectoryString( + DERPrintableString string) + { + this.string = string; + } + + private DirectoryString( + DERUniversalString string) + { + this.string = string; + } + + private DirectoryString( + DERUTF8String string) + { + this.string = string; + } + + private DirectoryString( + DERBMPString string) + { + this.string = string; + } + + public DirectoryString(String string) + { + this.string = new DERUTF8String(string); + } + + public String getString() + { + return string.getString(); + } + + public String toString() + { + return string.getString(); + } + + /** + *
+     *  DirectoryString ::= CHOICE {
+     *    teletexString               TeletexString (SIZE (1..MAX)),
+     *    printableString             PrintableString (SIZE (1..MAX)),
+     *    universalString             UniversalString (SIZE (1..MAX)),
+     *    utf8String                  UTF8String (SIZE (1..MAX)),
+     *    bmpString                   BMPString (SIZE (1..MAX))  }
+     * 
+ */ + public DERObject toASN1Object() + { + return ((DEREncodable)string).getDERObject(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/AccessDescription.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/AccessDescription.java new file mode 100644 index 000000000..db32d5a1e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/AccessDescription.java @@ -0,0 +1,98 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * The AccessDescription object. + *
+ * AccessDescription  ::=  SEQUENCE {
+ *       accessMethod          OBJECT IDENTIFIER,
+ *       accessLocation        GeneralName  }
+ * 
+ */ +public class AccessDescription + extends ASN1Encodable +{ + public final static DERObjectIdentifier id_ad_caIssuers = new DERObjectIdentifier("1.3.6.1.5.5.7.48.2"); + + public final static DERObjectIdentifier id_ad_ocsp = new DERObjectIdentifier("1.3.6.1.5.5.7.48.1"); + + DERObjectIdentifier accessMethod = null; + GeneralName accessLocation = null; + + public static AccessDescription getInstance( + Object obj) + { + if (obj instanceof AccessDescription) + { + return (AccessDescription)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new AccessDescription((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public AccessDescription( + ASN1Sequence seq) + { + if (seq.size() != 2) + { + throw new IllegalArgumentException("wrong number of elements in sequence"); + } + + accessMethod = DERObjectIdentifier.getInstance(seq.getObjectAt(0)); + accessLocation = GeneralName.getInstance(seq.getObjectAt(1)); + } + + /** + * create an AccessDescription with the oid and location provided. + */ + public AccessDescription( + DERObjectIdentifier oid, + GeneralName location) + { + accessMethod = oid; + accessLocation = location; + } + + /** + * + * @return the access method. + */ + public DERObjectIdentifier getAccessMethod() + { + return accessMethod; + } + + /** + * + * @return the access location + */ + public GeneralName getAccessLocation() + { + return accessLocation; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector accessDescription = new ASN1EncodableVector(); + + accessDescription.add(accessMethod); + accessDescription.add(accessLocation); + + return new DERSequence(accessDescription); + } + + public String toString() + { + return ("AccessDescription: Oid(" + this.accessMethod.getId() + ")"); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/AlgorithmIdentifier.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/AlgorithmIdentifier.java new file mode 100644 index 000000000..f1bb728f2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/AlgorithmIdentifier.java @@ -0,0 +1,126 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class AlgorithmIdentifier + extends ASN1Encodable +{ + private DERObjectIdentifier objectId; + private DEREncodable parameters; + private boolean parametersDefined = false; + + public static AlgorithmIdentifier getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static AlgorithmIdentifier getInstance( + Object obj) + { + if (obj== null || obj instanceof AlgorithmIdentifier) + { + return (AlgorithmIdentifier)obj; + } + + if (obj instanceof DERObjectIdentifier) + { + return new AlgorithmIdentifier((DERObjectIdentifier)obj); + } + + if (obj instanceof String) + { + return new AlgorithmIdentifier((String)obj); + } + + if (obj instanceof ASN1Sequence) + { + return new AlgorithmIdentifier((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public AlgorithmIdentifier( + DERObjectIdentifier objectId) + { + this.objectId = objectId; + } + + public AlgorithmIdentifier( + String objectId) + { + this.objectId = new DERObjectIdentifier(objectId); + } + + public AlgorithmIdentifier( + DERObjectIdentifier objectId, + DEREncodable parameters) + { + parametersDefined = true; + this.objectId = objectId; + this.parameters = parameters; + } + + public AlgorithmIdentifier( + ASN1Sequence seq) + { + if (seq.size() < 1 || seq.size() > 2) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + objectId = DERObjectIdentifier.getInstance(seq.getObjectAt(0)); + + if (seq.size() == 2) + { + parametersDefined = true; + parameters = seq.getObjectAt(1); + } + else + { + parameters = null; + } + } + + public DERObjectIdentifier getObjectId() + { + return objectId; + } + + public DEREncodable getParameters() + { + return parameters; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *      AlgorithmIdentifier ::= SEQUENCE {
+     *                            algorithm OBJECT IDENTIFIER,
+     *                            parameters ANY DEFINED BY algorithm OPTIONAL }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(objectId); + + if (parametersDefined) + { + v.add(parameters); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/AttCertIssuer.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/AttCertIssuer.java new file mode 100644 index 000000000..007903097 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/AttCertIssuer.java @@ -0,0 +1,90 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class AttCertIssuer + extends ASN1Encodable + implements ASN1Choice +{ + ASN1Encodable obj; + DERObject choiceObj; + + public static AttCertIssuer getInstance( + Object obj) + { + if (obj instanceof AttCertIssuer) + { + return (AttCertIssuer)obj; + } + else if (obj instanceof V2Form) + { + return new AttCertIssuer(V2Form.getInstance(obj)); + } + else if (obj instanceof GeneralNames) + { + return new AttCertIssuer((GeneralNames)obj); + } + else if (obj instanceof ASN1TaggedObject) + { + return new AttCertIssuer(V2Form.getInstance((ASN1TaggedObject)obj, false)); + } + else if (obj instanceof ASN1Sequence) + { + return new AttCertIssuer(GeneralNames.getInstance(obj)); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public static AttCertIssuer getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); // must be explictly tagged + } + + /** + * Don't use this one if you are trying to be RFC 3281 compliant. + * Use it for v1 attribute certificates only. + * + * @param names our GeneralNames structure + */ + public AttCertIssuer( + GeneralNames names) + { + obj = names; + choiceObj = obj.getDERObject(); + } + + public AttCertIssuer( + V2Form v2Form) + { + obj = v2Form; + choiceObj = new DERTaggedObject(false, 0, obj); + } + + public ASN1Encodable getIssuer() + { + return obj; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  AttCertIssuer ::= CHOICE {
+     *       v1Form   GeneralNames,  -- MUST NOT be used in this
+     *                               -- profile
+     *       v2Form   [0] V2Form     -- v2 only
+     *  }
+     * 
+ */ + public DERObject toASN1Object() + { + return choiceObj; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/AttCertValidityPeriod.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/AttCertValidityPeriod.java new file mode 100644 index 000000000..3bed5faa0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/AttCertValidityPeriod.java @@ -0,0 +1,84 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class AttCertValidityPeriod + extends ASN1Encodable +{ + DERGeneralizedTime notBeforeTime; + DERGeneralizedTime notAfterTime; + + public static AttCertValidityPeriod getInstance( + Object obj) + { + if (obj instanceof AttCertValidityPeriod) + { + return (AttCertValidityPeriod)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new AttCertValidityPeriod((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public AttCertValidityPeriod( + ASN1Sequence seq) + { + if (seq.size() != 2) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + notBeforeTime = DERGeneralizedTime.getInstance(seq.getObjectAt(0)); + notAfterTime = DERGeneralizedTime.getInstance(seq.getObjectAt(1)); + } + + /** + * @param notBeforeTime + * @param notAfterTime + */ + public AttCertValidityPeriod( + DERGeneralizedTime notBeforeTime, + DERGeneralizedTime notAfterTime) + { + this.notBeforeTime = notBeforeTime; + this.notAfterTime = notAfterTime; + } + + public DERGeneralizedTime getNotBeforeTime() + { + return notBeforeTime; + } + + public DERGeneralizedTime getNotAfterTime() + { + return notAfterTime; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  AttCertValidityPeriod  ::= SEQUENCE {
+     *       notBeforeTime  GeneralizedTime,
+     *       notAfterTime   GeneralizedTime
+     *  } 
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(notBeforeTime); + v.add(notAfterTime); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/Attribute.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/Attribute.java new file mode 100644 index 000000000..20e398f63 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/Attribute.java @@ -0,0 +1,87 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class Attribute + extends ASN1Encodable +{ + private DERObjectIdentifier attrType; + private ASN1Set attrValues; + + /** + * return an Attribute object from the given object. + * + * @param o the object we want converted. + * @exception IllegalArgumentException if the object cannot be converted. + */ + public static Attribute getInstance( + Object o) + { + if (o == null || o instanceof Attribute) + { + return (Attribute)o; + } + + if (o instanceof ASN1Sequence) + { + return new Attribute((ASN1Sequence)o); + } + + throw new IllegalArgumentException("unknown object in factory: " + o.getClass().getName()); + } + + public Attribute( + ASN1Sequence seq) + { + if (seq.size() != 2) + { + throw new IllegalArgumentException("Bad sequence size: " + seq.size()); + } + + attrType = DERObjectIdentifier.getInstance(seq.getObjectAt(0)); + attrValues = ASN1Set.getInstance(seq.getObjectAt(1)); + } + + public Attribute( + DERObjectIdentifier attrType, + ASN1Set attrValues) + { + this.attrType = attrType; + this.attrValues = attrValues; + } + + public DERObjectIdentifier getAttrType() + { + return attrType; + } + + public ASN1Set getAttrValues() + { + return attrValues; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * Attribute ::= SEQUENCE {
+     *     attrType OBJECT IDENTIFIER,
+     *     attrValues SET OF AttributeValue
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(attrType); + v.add(attrValues); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/AttributeCertificate.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/AttributeCertificate.java new file mode 100644 index 000000000..4d972990c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/AttributeCertificate.java @@ -0,0 +1,94 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class AttributeCertificate + extends ASN1Encodable +{ + AttributeCertificateInfo acinfo; + AlgorithmIdentifier signatureAlgorithm; + DERBitString signatureValue; + + /** + * @param obj + * @return an AttributeCertificate object + */ + public static AttributeCertificate getInstance(Object obj) + { + if (obj instanceof AttributeCertificate) + { + return (AttributeCertificate)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new AttributeCertificate((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public AttributeCertificate( + AttributeCertificateInfo acinfo, + AlgorithmIdentifier signatureAlgorithm, + DERBitString signatureValue) + { + this.acinfo = acinfo; + this.signatureAlgorithm = signatureAlgorithm; + this.signatureValue = signatureValue; + } + + public AttributeCertificate( + ASN1Sequence seq) + { + if (seq.size() != 3) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + this.acinfo = AttributeCertificateInfo.getInstance(seq.getObjectAt(0)); + this.signatureAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(1)); + this.signatureValue = DERBitString.getInstance(seq.getObjectAt(2)); + } + + public AttributeCertificateInfo getAcinfo() + { + return acinfo; + } + + public AlgorithmIdentifier getSignatureAlgorithm() + { + return signatureAlgorithm; + } + + public DERBitString getSignatureValue() + { + return signatureValue; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  AttributeCertificate ::= SEQUENCE {
+     *       acinfo               AttributeCertificateInfo,
+     *       signatureAlgorithm   AlgorithmIdentifier,
+     *       signatureValue       BIT STRING
+     *  }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(acinfo); + v.add(signatureAlgorithm); + v.add(signatureValue); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/AttributeCertificateInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/AttributeCertificateInfo.java new file mode 100644 index 000000000..8621b632b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/AttributeCertificateInfo.java @@ -0,0 +1,165 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class AttributeCertificateInfo + extends ASN1Encodable +{ + private DERInteger version; + private Holder holder; + private AttCertIssuer issuer; + private AlgorithmIdentifier signature; + private DERInteger serialNumber; + private AttCertValidityPeriod attrCertValidityPeriod; + private ASN1Sequence attributes; + private DERBitString issuerUniqueID; + private X509Extensions extensions; + + public static AttributeCertificateInfo getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static AttributeCertificateInfo getInstance( + Object obj) + { + if (obj instanceof AttributeCertificateInfo) + { + return (AttributeCertificateInfo)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new AttributeCertificateInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public AttributeCertificateInfo( + ASN1Sequence seq) + { + if (seq.size() < 7 || seq.size() > 9) + { + throw new IllegalArgumentException("Bad sequence size: " + seq.size()); + } + + this.version = DERInteger.getInstance(seq.getObjectAt(0)); + this.holder = Holder.getInstance(seq.getObjectAt(1)); + this.issuer = AttCertIssuer.getInstance(seq.getObjectAt(2)); + this.signature = AlgorithmIdentifier.getInstance(seq.getObjectAt(3)); + this.serialNumber = DERInteger.getInstance(seq.getObjectAt(4)); + this.attrCertValidityPeriod = AttCertValidityPeriod.getInstance(seq.getObjectAt(5)); + this.attributes = ASN1Sequence.getInstance(seq.getObjectAt(6)); + + for (int i = 7; i < seq.size(); i++) + { + ASN1Encodable obj = (ASN1Encodable)seq.getObjectAt(i); + + if (obj instanceof DERBitString) + { + this.issuerUniqueID = DERBitString.getInstance(seq.getObjectAt(i)); + } + else if (obj instanceof ASN1Sequence || obj instanceof X509Extensions) + { + this.extensions = X509Extensions.getInstance(seq.getObjectAt(i)); + } + } + } + + public DERInteger getVersion() + { + return version; + } + + public Holder getHolder() + { + return holder; + } + + public AttCertIssuer getIssuer() + { + return issuer; + } + + public AlgorithmIdentifier getSignature() + { + return signature; + } + + public DERInteger getSerialNumber() + { + return serialNumber; + } + + public AttCertValidityPeriod getAttrCertValidityPeriod() + { + return attrCertValidityPeriod; + } + + public ASN1Sequence getAttributes() + { + return attributes; + } + + public DERBitString getIssuerUniqueID() + { + return issuerUniqueID; + } + + public X509Extensions getExtensions() + { + return extensions; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  AttributeCertificateInfo ::= SEQUENCE {
+     *       version              AttCertVersion -- version is v2,
+     *       holder               Holder,
+     *       issuer               AttCertIssuer,
+     *       signature            AlgorithmIdentifier,
+     *       serialNumber         CertificateSerialNumber,
+     *       attrCertValidityPeriod   AttCertValidityPeriod,
+     *       attributes           SEQUENCE OF Attribute,
+     *       issuerUniqueID       UniqueIdentifier OPTIONAL,
+     *       extensions           Extensions OPTIONAL
+     *  }
+     *
+     *  AttCertVersion ::= INTEGER { v2(1) }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(holder); + v.add(issuer); + v.add(signature); + v.add(serialNumber); + v.add(attrCertValidityPeriod); + v.add(attributes); + + if (issuerUniqueID != null) + { + v.add(issuerUniqueID); + } + + if (extensions != null) + { + v.add(extensions); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/AuthorityInformationAccess.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/AuthorityInformationAccess.java new file mode 100644 index 000000000..cef254f87 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/AuthorityInformationAccess.java @@ -0,0 +1,106 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * The AuthorityInformationAccess object. + *
+ * id-pe-authorityInfoAccess OBJECT IDENTIFIER ::= { id-pe 1 }
+ *
+ * AuthorityInfoAccessSyntax  ::=
+ *      SEQUENCE SIZE (1..MAX) OF AccessDescription
+ * AccessDescription  ::=  SEQUENCE {
+ *       accessMethod          OBJECT IDENTIFIER,
+ *       accessLocation        GeneralName  }
+ *
+ * id-ad OBJECT IDENTIFIER ::= { id-pkix 48 }
+ * id-ad-caIssuers OBJECT IDENTIFIER ::= { id-ad 2 }
+ * id-ad-ocsp OBJECT IDENTIFIER ::= { id-ad 1 }
+ * 
+ */ +public class AuthorityInformationAccess + extends ASN1Encodable +{ + private AccessDescription[] descriptions; + + public static AuthorityInformationAccess getInstance( + Object obj) + { + if (obj instanceof AuthorityInformationAccess) + { + return (AuthorityInformationAccess)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new AuthorityInformationAccess((ASN1Sequence)obj); + } + + if (obj instanceof X509Extension) + { + return getInstance(X509Extension.convertValueToObject((X509Extension)obj)); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public AuthorityInformationAccess( + ASN1Sequence seq) + { + if (seq.size() < 1) + { + throw new IllegalArgumentException("sequence may not be empty"); + } + + descriptions = new AccessDescription[seq.size()]; + + for (int i = 0; i != seq.size(); i++) + { + descriptions[i] = AccessDescription.getInstance(seq.getObjectAt(i)); + } + } + + /** + * create an AuthorityInformationAccess with the oid and location provided. + */ + public AuthorityInformationAccess( + DERObjectIdentifier oid, + GeneralName location) + { + descriptions = new AccessDescription[1]; + + descriptions[0] = new AccessDescription(oid, location); + } + + + /** + * + * @return the access descriptions contained in this object. + */ + public AccessDescription[] getAccessDescriptions() + { + return descriptions; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector vec = new ASN1EncodableVector(); + + for (int i = 0; i != descriptions.length; i++) + { + vec.add(descriptions[i]); + } + + return new DERSequence(vec); + } + + public String toString() + { + return ("AuthorityInformationAccess: Oid(" + this.descriptions[0].getAccessMethod().getId() + ")"); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/AuthorityKeyIdentifier.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/AuthorityKeyIdentifier.java new file mode 100644 index 000000000..4d8da3971 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/AuthorityKeyIdentifier.java @@ -0,0 +1,231 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.digests.SHA1Digest; + +import java.math.BigInteger; +import java.util.Enumeration; + +/** + * The AuthorityKeyIdentifier object. + *
+ * id-ce-authorityKeyIdentifier OBJECT IDENTIFIER ::=  { id-ce 35 }
+ *
+ *   AuthorityKeyIdentifier ::= SEQUENCE {
+ *      keyIdentifier             [0] IMPLICIT KeyIdentifier           OPTIONAL,
+ *      authorityCertIssuer       [1] IMPLICIT GeneralNames            OPTIONAL,
+ *      authorityCertSerialNumber [2] IMPLICIT CertificateSerialNumber OPTIONAL  }
+ *
+ *   KeyIdentifier ::= OCTET STRING
+ * 
+ * + */ +public class AuthorityKeyIdentifier + extends ASN1Encodable +{ + ASN1OctetString keyidentifier=null; + GeneralNames certissuer=null; + DERInteger certserno=null; + + public static AuthorityKeyIdentifier getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static AuthorityKeyIdentifier getInstance( + Object obj) + { + if (obj instanceof AuthorityKeyIdentifier) + { + return (AuthorityKeyIdentifier)obj; + } + if (obj instanceof ASN1Sequence) + { + return new AuthorityKeyIdentifier((ASN1Sequence)obj); + } + if (obj instanceof X509Extension) + { + return getInstance(X509Extension.convertValueToObject((X509Extension)obj)); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public AuthorityKeyIdentifier( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + while (e.hasMoreElements()) + { + ASN1TaggedObject o = DERTaggedObject.getInstance(e.nextElement()); + + switch (o.getTagNo()) + { + case 0: + this.keyidentifier = ASN1OctetString.getInstance(o, false); + break; + case 1: + this.certissuer = GeneralNames.getInstance(o, false); + break; + case 2: + this.certserno = DERInteger.getInstance(o, false); + break; + default: + throw new IllegalArgumentException("illegal tag"); + } + } + } + + /** + * + * Calulates the keyidentifier using a SHA1 hash over the BIT STRING + * from SubjectPublicKeyInfo as defined in RFC2459. + * + * Example of making a AuthorityKeyIdentifier: + *
+     *   SubjectPublicKeyInfo apki = new SubjectPublicKeyInfo((ASN1Sequence)new ASN1InputStream(
+     *       publicKey.getEncoded()).readObject());
+     *   AuthorityKeyIdentifier aki = new AuthorityKeyIdentifier(apki);
+     * 
+ * + **/ + public AuthorityKeyIdentifier( + SubjectPublicKeyInfo spki) + { + Digest digest = new SHA1Digest(); + byte[] resBuf = new byte[digest.getDigestSize()]; + + byte[] bytes = spki.getPublicKeyData().getBytes(); + digest.update(bytes, 0, bytes.length); + digest.doFinal(resBuf, 0); + this.keyidentifier = new DEROctetString(resBuf); + } + + /** + * create an AuthorityKeyIdentifier with the GeneralNames tag and + * the serial number provided as well. + */ + public AuthorityKeyIdentifier( + SubjectPublicKeyInfo spki, + GeneralNames name, + BigInteger serialNumber) + { + Digest digest = new SHA1Digest(); + byte[] resBuf = new byte[digest.getDigestSize()]; + + byte[] bytes = spki.getPublicKeyData().getBytes(); + digest.update(bytes, 0, bytes.length); + digest.doFinal(resBuf, 0); + + this.keyidentifier = new DEROctetString(resBuf); + this.certissuer = GeneralNames.getInstance(name.toASN1Object()); + this.certserno = new DERInteger(serialNumber); + } + + /** + * create an AuthorityKeyIdentifier with the GeneralNames tag and + * the serial number provided. + */ + public AuthorityKeyIdentifier( + GeneralNames name, + BigInteger serialNumber) + { + this.keyidentifier = null; + this.certissuer = GeneralNames.getInstance(name.toASN1Object()); + this.certserno = new DERInteger(serialNumber); + } + + /** + * create an AuthorityKeyIdentifier with a precomupted key identifier + */ + public AuthorityKeyIdentifier( + byte[] keyIdentifier) + { + this.keyidentifier = new DEROctetString(keyIdentifier); + this.certissuer = null; + this.certserno = null; + } + + /** + * create an AuthorityKeyIdentifier with a precomupted key identifier + * and the GeneralNames tag and the serial number provided as well. + */ + public AuthorityKeyIdentifier( + byte[] keyIdentifier, + GeneralNames name, + BigInteger serialNumber) + { + this.keyidentifier = new DEROctetString(keyIdentifier); + this.certissuer = GeneralNames.getInstance(name.toASN1Object()); + this.certserno = new DERInteger(serialNumber); + } + + public byte[] getKeyIdentifier() + { + if (keyidentifier != null) + { + return keyidentifier.getOctets(); + } + + return null; + } + + public GeneralNames getAuthorityCertIssuer() + { + return certissuer; + } + + public BigInteger getAuthorityCertSerialNumber() + { + if (certserno != null) + { + return certserno.getValue(); + } + + return null; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (keyidentifier != null) + { + v.add(new DERTaggedObject(false, 0, keyidentifier)); + } + + if (certissuer != null) + { + v.add(new DERTaggedObject(false, 1, certissuer)); + } + + if (certserno != null) + { + v.add(new DERTaggedObject(false, 2, certserno)); + } + + + return new DERSequence(v); + } + + public String toString() + { + return ("AuthorityKeyIdentifier: KeyID(" + this.keyidentifier.getOctets() + ")"); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/BasicConstraints.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/BasicConstraints.java new file mode 100644 index 000000000..8071cbdb2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/BasicConstraints.java @@ -0,0 +1,181 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBoolean; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +import java.math.BigInteger; + +public class BasicConstraints + extends ASN1Encodable +{ + DERBoolean cA = new DERBoolean(false); + DERInteger pathLenConstraint = null; + + public static BasicConstraints getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static BasicConstraints getInstance( + Object obj) + { + if (obj == null || obj instanceof BasicConstraints) + { + return (BasicConstraints)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new BasicConstraints((ASN1Sequence)obj); + } + + if (obj instanceof X509Extension) + { + return getInstance(X509Extension.convertValueToObject((X509Extension)obj)); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public BasicConstraints( + ASN1Sequence seq) + { + if (seq.size() == 0) + { + this.cA = null; + this.pathLenConstraint = null; + } + else + { + if (seq.getObjectAt(0) instanceof DERBoolean) + { + this.cA = DERBoolean.getInstance(seq.getObjectAt(0)); + } + else + { + this.cA = null; + this.pathLenConstraint = DERInteger.getInstance(seq.getObjectAt(0)); + } + if (seq.size() > 1) + { + if (this.cA != null) + { + this.pathLenConstraint = DERInteger.getInstance(seq.getObjectAt(1)); + } + else + { + throw new IllegalArgumentException("wrong sequence in constructor"); + } + } + } + } + + /** + * @deprecated use one of the other two unambigous constructors. + * @param cA + * @param pathLenConstraint + */ + public BasicConstraints( + boolean cA, + int pathLenConstraint) + { + if (cA) + { + this.cA = new DERBoolean(cA); + this.pathLenConstraint = new DERInteger(pathLenConstraint); + } + else + { + this.cA = null; + this.pathLenConstraint = null; + } + } + + public BasicConstraints( + boolean cA) + { + if (cA) + { + this.cA = new DERBoolean(true); + } + else + { + this.cA = null; + } + this.pathLenConstraint = null; + } + + /** + * create a cA=true object for the given path length constraint. + * + * @param pathLenConstraint + */ + public BasicConstraints( + int pathLenConstraint) + { + this.cA = new DERBoolean(true); + this.pathLenConstraint = new DERInteger(pathLenConstraint); + } + + public boolean isCA() + { + return (cA != null) && cA.isTrue(); + } + + public BigInteger getPathLenConstraint() + { + if (pathLenConstraint != null) + { + return pathLenConstraint.getValue(); + } + + return null; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * BasicConstraints := SEQUENCE {
+     *    cA                  BOOLEAN DEFAULT FALSE,
+     *    pathLenConstraint   INTEGER (0..MAX) OPTIONAL
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (cA != null) + { + v.add(cA); + } + + if (pathLenConstraint != null) // yes some people actually do this when cA is false... + { + v.add(pathLenConstraint); + } + + return new DERSequence(v); + } + + public String toString() + { + if (pathLenConstraint == null) + { + if (cA == null) + { + return "BasicConstraints: isCa(false)"; + } + return "BasicConstraints: isCa(" + this.isCA() + ")"; + } + return "BasicConstraints: isCa(" + this.isCA() + "), pathLenConstraint = " + pathLenConstraint.getValue(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/CRLDistPoint.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/CRLDistPoint.java new file mode 100644 index 000000000..230133bee --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/CRLDistPoint.java @@ -0,0 +1,100 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class CRLDistPoint + extends ASN1Encodable +{ + ASN1Sequence seq = null; + + public static CRLDistPoint getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static CRLDistPoint getInstance( + Object obj) + { + if (obj instanceof CRLDistPoint || obj == null) + { + return (CRLDistPoint)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new CRLDistPoint((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public CRLDistPoint( + ASN1Sequence seq) + { + this.seq = seq; + } + + public CRLDistPoint( + DistributionPoint[] points) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + for (int i = 0; i != points.length; i++) + { + v.add(points[i]); + } + + seq = new DERSequence(v); + } + + /** + * Return the distribution points making up the sequence. + * + * @return DistributionPoint[] + */ + public DistributionPoint[] getDistributionPoints() + { + DistributionPoint[] dp = new DistributionPoint[seq.size()]; + + for (int i = 0; i != seq.size(); i++) + { + dp[i] = DistributionPoint.getInstance(seq.getObjectAt(i)); + } + + return dp; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * CRLDistPoint ::= SEQUENCE SIZE {1..MAX} OF DistributionPoint
+     * 
+ */ + public DERObject toASN1Object() + { + return seq; + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + String sep = System.getProperty("line.separator"); + + buf.append("CRLDistPoint:"); + buf.append(sep); + DistributionPoint dp[] = getDistributionPoints(); + for (int i = 0; i != dp.length; i++) + { + buf.append(" "); + buf.append(dp[i]); + buf.append(sep); + } + return buf.toString(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/CRLNumber.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/CRLNumber.java new file mode 100644 index 000000000..b518487df --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/CRLNumber.java @@ -0,0 +1,32 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.DERInteger; + +import java.math.BigInteger; + +/** + * The CRLNumber object. + *
+ * CRLNumber::= INTEGER(0..MAX)
+ * 
+ */ +public class CRLNumber + extends DERInteger +{ + + public CRLNumber( + BigInteger number) + { + super(number); + } + + public BigInteger getCRLNumber() + { + return getPositiveValue(); + } + + public String toString() + { + return "CRLNumber: " + getCRLNumber(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/CRLReason.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/CRLReason.java new file mode 100644 index 000000000..f4e27688e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/CRLReason.java @@ -0,0 +1,111 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.DEREnumerated; + +/** + * The CRLReason enumeration. + *
+ * CRLReason ::= ENUMERATED {
+ *  unspecified             (0),
+ *  keyCompromise           (1),
+ *  cACompromise            (2),
+ *  affiliationChanged      (3),
+ *  superseded              (4),
+ *  cessationOfOperation    (5),
+ *  certificateHold         (6),
+ *  removeFromCRL           (8),
+ *  privilegeWithdrawn      (9),
+ *  aACompromise           (10)
+ * }
+ * 
+ */ +public class CRLReason + extends DEREnumerated +{ + /** + * @deprecated use lower case version + */ + public static final int UNSPECIFIED = 0; + /** + * @deprecated use lower case version + */ + public static final int KEY_COMPROMISE = 1; + /** + * @deprecated use lower case version + */ + public static final int CA_COMPROMISE = 2; + /** + * @deprecated use lower case version + */ + public static final int AFFILIATION_CHANGED = 3; + /** + * @deprecated use lower case version + */ + public static final int SUPERSEDED = 4; + /** + * @deprecated use lower case version + */ + public static final int CESSATION_OF_OPERATION = 5; + /** + * @deprecated use lower case version + */ + public static final int CERTIFICATE_HOLD = 6; + /** + * @deprecated use lower case version + */ + public static final int REMOVE_FROM_CRL = 8; + /** + * @deprecated use lower case version + */ + public static final int PRIVILEGE_WITHDRAWN = 9; + /** + * @deprecated use lower case version + */ + public static final int AA_COMPROMISE = 10; + + public static final int unspecified = 0; + public static final int keyCompromise = 1; + public static final int cACompromise = 2; + public static final int affiliationChanged = 3; + public static final int superseded = 4; + public static final int cessationOfOperation = 5; + public static final int certificateHold = 6; + // 7 -> unknown + public static final int removeFromCRL = 8; + public static final int privilegeWithdrawn = 9; + public static final int aACompromise = 10; + + private static final String[] reasonString = + { + "unspecified", "keyCompromise", "cACompromise", "affiliationChanged", + "superseded", "cessationOfOperation", "certificateHold", "unknown", + "removeFromCRL", "privilegeWithdrawn", "aACompromise" + }; + + public CRLReason( + int reason) + { + super(reason); + } + + public CRLReason( + DEREnumerated reason) + { + super(reason.getValue().intValue()); + } + + public String toString() + { + String str; + int reason = getValue().intValue(); + if (reason < 0 || reason > 10) + { + str = "invalid"; + } + else + { + str = reasonString[reason]; + } + return "CRLReason: " + str; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/CertPolicyId.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/CertPolicyId.java new file mode 100644 index 000000000..89705a0a6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/CertPolicyId.java @@ -0,0 +1,20 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + + +/** + * CertPolicyId, used in the CertificatePolicies and PolicyMappings + * X509V3 Extensions. + * + *
+ *     CertPolicyId ::= OBJECT IDENTIFIER
+ * 
+ */ +public class CertPolicyId extends DERObjectIdentifier +{ + public CertPolicyId (String id) + { + super(id); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/CertificateList.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/CertificateList.java new file mode 100644 index 000000000..fd32e1c53 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/CertificateList.java @@ -0,0 +1,126 @@ + +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +import java.util.Enumeration; + +/** + * PKIX RFC-2459 + * + * The X.509 v2 CRL syntax is as follows. For signature calculation, + * the data that is to be signed is ASN.1 DER encoded. + * + *
+ * CertificateList  ::=  SEQUENCE  {
+ *      tbsCertList          TBSCertList,
+ *      signatureAlgorithm   AlgorithmIdentifier,
+ *      signatureValue       BIT STRING  }
+ * 
+ */ +public class CertificateList + extends ASN1Encodable +{ + TBSCertList tbsCertList; + AlgorithmIdentifier sigAlgId; + DERBitString sig; + + public static CertificateList getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static CertificateList getInstance( + Object obj) + { + if (obj instanceof CertificateList) + { + return (CertificateList)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new CertificateList((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public CertificateList( + ASN1Sequence seq) + { + if (seq.size() == 3) + { + tbsCertList = TBSCertList.getInstance(seq.getObjectAt(0)); + sigAlgId = AlgorithmIdentifier.getInstance(seq.getObjectAt(1)); + sig = DERBitString.getInstance(seq.getObjectAt(2)); + } + else + { + throw new IllegalArgumentException("sequence wrong size for CertificateList"); + } + } + + public TBSCertList getTBSCertList() + { + return tbsCertList; + } + + public TBSCertList.CRLEntry[] getRevokedCertificates() + { + return tbsCertList.getRevokedCertificates(); + } + + public Enumeration getRevokedCertificateEnumeration() + { + return tbsCertList.getRevokedCertificateEnumeration(); + } + + public AlgorithmIdentifier getSignatureAlgorithm() + { + return sigAlgId; + } + + public DERBitString getSignature() + { + return sig; + } + + public int getVersion() + { + return tbsCertList.getVersion(); + } + + public X509Name getIssuer() + { + return tbsCertList.getIssuer(); + } + + public Time getThisUpdate() + { + return tbsCertList.getThisUpdate(); + } + + public Time getNextUpdate() + { + return tbsCertList.getNextUpdate(); + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(tbsCertList); + v.add(sigAlgId); + v.add(sig); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/CertificatePair.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/CertificatePair.java new file mode 100644 index 000000000..35f97dcce --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/CertificatePair.java @@ -0,0 +1,169 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +import java.util.Enumeration; + +/** + * This class helps to support crossCerfificatePairs in a LDAP directory + * according RFC 2587 + * + *
+ *     crossCertificatePairATTRIBUTE::={
+ *       WITH SYNTAX   CertificatePair
+ *       EQUALITY MATCHING RULE certificatePairExactMatch
+ *       ID joint-iso-ccitt(2) ds(5) attributeType(4) crossCertificatePair(40)}
+ * 
+ * + *
The forward elements of the crossCertificatePair attribute of a + * CA's directory entry shall be used to store all, except self-issued + * certificates issued to this CA. Optionally, the reverse elements of the + * crossCertificatePair attribute, of a CA's directory entry may contain a + * subset of certificates issued by this CA to other CAs. When both the forward + * and the reverse elements are present in a single attribute value, issuer name + * in one certificate shall match the subject name in the other and vice versa, + * and the subject public key in one certificate shall be capable of verifying + * the digital signature on the other certificate and vice versa. + * + * When a reverse element is present, the forward element value and the reverse + * element value need not be stored in the same attribute value; in other words, + * they can be stored in either a single attribute value or two attribute + * values.
+ * + *
+ *       CertificatePair ::= SEQUENCE {
+ *         forward        [0]    Certificate OPTIONAL,
+ *         reverse        [1]    Certificate OPTIONAL,
+ *         -- at least one of the pair shall be present -- } 
+ * 
+ */ +public class CertificatePair + extends ASN1Encodable +{ + private X509CertificateStructure forward; + + private X509CertificateStructure reverse; + + public static CertificatePair getInstance(Object obj) + { + if (obj == null || obj instanceof CertificatePair) + { + return (CertificatePair)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new CertificatePair((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + + obj.getClass().getName()); + } + + /** + * Constructor from ASN1Sequence. + *

+ * The sequence is of type CertificatePair: + *

+ *

+     *       CertificatePair ::= SEQUENCE {
+     *         forward        [0]    Certificate OPTIONAL,
+     *         reverse        [1]    Certificate OPTIONAL,
+     *         -- at least one of the pair shall be present -- }
+     * 
+ * + * @param seq The ASN.1 sequence. + */ + private CertificatePair(ASN1Sequence seq) + { + if (seq.size() != 1 && seq.size() != 2) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + Enumeration e = seq.getObjects(); + + while (e.hasMoreElements()) + { + ASN1TaggedObject o = ASN1TaggedObject.getInstance(e.nextElement()); + if (o.getTagNo() == 0) + { + forward = X509CertificateStructure.getInstance(o, true); + } + else if (o.getTagNo() == 1) + { + reverse = X509CertificateStructure.getInstance(o, true); + } + else + { + throw new IllegalArgumentException("Bad tag number: " + + o.getTagNo()); + } + } + } + + /** + * Constructor from a given details. + * + * @param forward Certificates issued to this CA. + * @param reverse Certificates issued by this CA to other CAs. + */ + public CertificatePair(X509CertificateStructure forward, X509CertificateStructure reverse) + { + this.forward = forward; + this.reverse = reverse; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+ * Returns: + *

+ *

+     *       CertificatePair ::= SEQUENCE {
+     *         forward        [0]    Certificate OPTIONAL,
+     *         reverse        [1]    Certificate OPTIONAL,
+     *         -- at least one of the pair shall be present -- }
+     * 
+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + ASN1EncodableVector vec = new ASN1EncodableVector(); + + if (forward != null) + { + vec.add(new DERTaggedObject(0, forward)); + } + if (reverse != null) + { + vec.add(new DERTaggedObject(1, reverse)); + } + + return new DERSequence(vec); + } + + /** + * @return Returns the forward. + */ + public X509CertificateStructure getForward() + { + return forward; + } + + /** + * @return Returns the reverse. + */ + public X509CertificateStructure getReverse() + { + return reverse; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/CertificatePolicies.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/CertificatePolicies.java new file mode 100644 index 000000000..0e19a25f2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/CertificatePolicies.java @@ -0,0 +1,147 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import java.util.Enumeration; +import java.util.Vector; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class CertificatePolicies + extends ASN1Encodable +{ + static final DERObjectIdentifier anyPolicy = new DERObjectIdentifier("2.5.29.32.0"); + + Vector policies = new Vector(); + +/** + * @deprecated use an ASN1Sequence of PolicyInformation + */ + public static CertificatePolicies getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + +/** + * @deprecated use an ASN1Sequence of PolicyInformation + */ + public static CertificatePolicies getInstance( + Object obj) + { + if (obj instanceof CertificatePolicies) + { + return (CertificatePolicies)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new CertificatePolicies((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + +/** + * @deprecated use an ASN1Sequence of PolicyInformation + */ + public CertificatePolicies( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + while (e.hasMoreElements()) + { + ASN1Sequence s = ASN1Sequence.getInstance(e.nextElement()); + policies.addElement(s.getObjectAt(0)); + } + // For now we just don't handle PolicyQualifiers + } + + /** + * create a certificate policy with the given OID. + * @deprecated use an ASN1Sequence of PolicyInformation + */ + public CertificatePolicies( + DERObjectIdentifier p) + { + policies.addElement(p); + } + + /** + * create a certificate policy with the policy given by the OID represented + * by the string p. + * @deprecated use an ASN1Sequence of PolicyInformation + */ + public CertificatePolicies( + String p) + { + this(new DERObjectIdentifier(p)); + } + + public void addPolicy( + String p) + { + policies.addElement(new DERObjectIdentifier(p)); + } + + public String getPolicy(int nr) + { + if (policies.size() > nr) + { + return ((DERObjectIdentifier)policies.elementAt(nr)).getId(); + } + + return null; + } + + /** + *
+     * certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
+     *
+     * PolicyInformation ::= SEQUENCE {
+     *   policyIdentifier   CertPolicyId,
+     *   policyQualifiers   SEQUENCE SIZE (1..MAX) OF
+     *                           PolicyQualifierInfo OPTIONAL }
+     *
+     * CertPolicyId ::= OBJECT IDENTIFIER
+     *
+     * PolicyQualifierInfo ::= SEQUENCE {
+     *   policyQualifierId  PolicyQualifierId,
+     *   qualifier          ANY DEFINED BY policyQualifierId }
+     *
+     * PolicyQualifierId ::=
+     *   OBJECT IDENTIFIER (id-qt-cps | id-qt-unotice)
+     * 
+ * @deprecated use an ASN1Sequence of PolicyInformation + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + // We only do policyIdentifier yet... + for (int i=0;i + * DigestInfo::=SEQUENCE{ + * digestAlgorithm AlgorithmIdentifier, + * digest OCTET STRING } + * + */ +public class DigestInfo + extends ASN1Encodable +{ + private byte[] digest; + private AlgorithmIdentifier algId; + + public static DigestInfo getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static DigestInfo getInstance( + Object obj) + { + if (obj instanceof DigestInfo) + { + return (DigestInfo)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new DigestInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public DigestInfo( + AlgorithmIdentifier algId, + byte[] digest) + { + this.digest = digest; + this.algId = algId; + } + + public DigestInfo( + ASN1Sequence obj) + { + Enumeration e = obj.getObjects(); + + algId = AlgorithmIdentifier.getInstance(e.nextElement()); + digest = ASN1OctetString.getInstance(e.nextElement()).getOctets(); + } + + public AlgorithmIdentifier getAlgorithmId() + { + return algId; + } + + public byte[] getDigest() + { + return digest; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(algId); + v.add(new DEROctetString(digest)); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/DisplayText.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/DisplayText.java new file mode 100644 index 000000000..d0ecae58f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/DisplayText.java @@ -0,0 +1,165 @@ + +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBMPString; +import com.google.bitcoin.bouncycastle.asn1.DERIA5String; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERString; +import com.google.bitcoin.bouncycastle.asn1.DERUTF8String; +import com.google.bitcoin.bouncycastle.asn1.DERVisibleString; + +/** + * DisplayText class, used in + * CertificatePolicies X509 V3 extensions (in policy qualifiers). + * + *

It stores a string in a chosen encoding. + *

+ * DisplayText ::= CHOICE {
+ *      ia5String        IA5String      (SIZE (1..200)),
+ *      visibleString    VisibleString  (SIZE (1..200)),
+ *      bmpString        BMPString      (SIZE (1..200)),
+ *      utf8String       UTF8String     (SIZE (1..200)) }
+ * 
+ * @see PolicyQualifierInfo + * @see PolicyInformation + */ +public class DisplayText + extends ASN1Encodable + implements ASN1Choice +{ + /** + * Constant corresponding to ia5String encoding. + * + */ + public static final int CONTENT_TYPE_IA5STRING = 0; + /** + * Constant corresponding to bmpString encoding. + * + */ + public static final int CONTENT_TYPE_BMPSTRING = 1; + /** + * Constant corresponding to utf8String encoding. + * + */ + public static final int CONTENT_TYPE_UTF8STRING = 2; + /** + * Constant corresponding to visibleString encoding. + * + */ + public static final int CONTENT_TYPE_VISIBLESTRING = 3; + + /** + * Describe constant DISPLAY_TEXT_MAXIMUM_SIZE here. + * + */ + public static final int DISPLAY_TEXT_MAXIMUM_SIZE = 200; + + int contentType; + DERString contents; + + /** + * Creates a new DisplayText instance. + * + * @param type the desired encoding type for the text. + * @param text the text to store. Strings longer than 200 + * characters are truncated. + */ + public DisplayText (int type, String text) + { + if (text.length() > DISPLAY_TEXT_MAXIMUM_SIZE) + { + // RFC3280 limits these strings to 200 chars + // truncate the string + text = text.substring (0, DISPLAY_TEXT_MAXIMUM_SIZE); + } + + contentType = type; + switch (type) + { + case CONTENT_TYPE_IA5STRING: + contents = (DERString)new DERIA5String (text); + break; + case CONTENT_TYPE_UTF8STRING: + contents = (DERString)new DERUTF8String(text); + break; + case CONTENT_TYPE_VISIBLESTRING: + contents = (DERString)new DERVisibleString(text); + break; + case CONTENT_TYPE_BMPSTRING: + contents = (DERString)new DERBMPString(text); + break; + default: + contents = (DERString)new DERUTF8String(text); + break; + } + } + + /** + * Creates a new DisplayText instance. + * + * @param text the text to encapsulate. Strings longer than 200 + * characters are truncated. + */ + public DisplayText (String text) + { + // by default use UTF8String + if (text.length() > DISPLAY_TEXT_MAXIMUM_SIZE) + { + text = text.substring(0, DISPLAY_TEXT_MAXIMUM_SIZE); + } + + contentType = CONTENT_TYPE_UTF8STRING; + contents = new DERUTF8String(text); + } + + /** + * Creates a new DisplayText instance. + *

Useful when reading back a DisplayText class + * from it's ASN1Encodable/DEREncodable form. + * + * @param de a DEREncodable instance. + */ + public DisplayText(DERString de) + { + contents = de; + } + + public static DisplayText getInstance(Object obj) + { + if (obj instanceof DERString) + { + return new DisplayText((DERString)obj); + } + else if (obj instanceof DisplayText) + { + return (DisplayText)obj; + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + public static DisplayText getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); // must be explicitly tagged + } + + public DERObject toASN1Object() + { + return (DERObject)contents; + } + + /** + * Returns the stored String object. + * + * @return the stored text as a String. + */ + public String getString() + { + return contents.getString(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/DistributionPoint.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/DistributionPoint.java new file mode 100644 index 000000000..77293400a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/DistributionPoint.java @@ -0,0 +1,158 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +/** + * The DistributionPoint object. + *

+ * DistributionPoint ::= SEQUENCE {
+ *      distributionPoint [0] DistributionPointName OPTIONAL,
+ *      reasons           [1] ReasonFlags OPTIONAL,
+ *      cRLIssuer         [2] GeneralNames OPTIONAL
+ * }
+ * 
+ */ +public class DistributionPoint + extends ASN1Encodable +{ + DistributionPointName distributionPoint; + ReasonFlags reasons; + GeneralNames cRLIssuer; + + public static DistributionPoint getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static DistributionPoint getInstance( + Object obj) + { + if(obj == null || obj instanceof DistributionPoint) + { + return (DistributionPoint)obj; + } + + if(obj instanceof ASN1Sequence) + { + return new DistributionPoint((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid DistributionPoint: " + obj.getClass().getName()); + } + + public DistributionPoint( + ASN1Sequence seq) + { + for (int i = 0; i != seq.size(); i++) + { + ASN1TaggedObject t = ASN1TaggedObject.getInstance(seq.getObjectAt(i)); + switch (t.getTagNo()) + { + case 0: + distributionPoint = DistributionPointName.getInstance(t, true); + break; + case 1: + reasons = new ReasonFlags(DERBitString.getInstance(t, false)); + break; + case 2: + cRLIssuer = GeneralNames.getInstance(t, false); + } + } + } + + public DistributionPoint( + DistributionPointName distributionPoint, + ReasonFlags reasons, + GeneralNames cRLIssuer) + { + this.distributionPoint = distributionPoint; + this.reasons = reasons; + this.cRLIssuer = cRLIssuer; + } + + public DistributionPointName getDistributionPoint() + { + return distributionPoint; + } + + public ReasonFlags getReasons() + { + return reasons; + } + + public GeneralNames getCRLIssuer() + { + return cRLIssuer; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (distributionPoint != null) + { + // + // as this is a CHOICE it must be explicitly tagged + // + v.add(new DERTaggedObject(0, distributionPoint)); + } + + if (reasons != null) + { + v.add(new DERTaggedObject(false, 1, reasons)); + } + + if (cRLIssuer != null) + { + v.add(new DERTaggedObject(false, 2, cRLIssuer)); + } + + return new DERSequence(v); + } + + public String toString() + { + String sep = System.getProperty("line.separator"); + StringBuffer buf = new StringBuffer(); + buf.append("DistributionPoint: ["); + buf.append(sep); + if (distributionPoint != null) + { + appendObject(buf, sep, "distributionPoint", distributionPoint.toString()); + } + if (reasons != null) + { + appendObject(buf, sep, "reasons", reasons.toString()); + } + if (cRLIssuer != null) + { + appendObject(buf, sep, "cRLIssuer", cRLIssuer.toString()); + } + buf.append("]"); + buf.append(sep); + return buf.toString(); + } + + private void appendObject(StringBuffer buf, String sep, String name, String value) + { + String indent = " "; + + buf.append(indent); + buf.append(name); + buf.append(":"); + buf.append(sep); + buf.append(indent); + buf.append(indent); + buf.append(value); + buf.append(sep); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/DistributionPointName.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/DistributionPointName.java new file mode 100644 index 000000000..e7617b754 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/DistributionPointName.java @@ -0,0 +1,149 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +/** + * The DistributionPointName object. + *
+ * DistributionPointName ::= CHOICE {
+ *     fullName                 [0] GeneralNames,
+ *     nameRelativeToCRLIssuer  [1] RelativeDistinguishedName
+ * }
+ * 
+ */ +public class DistributionPointName + extends ASN1Encodable + implements ASN1Choice +{ + DEREncodable name; + int type; + + public static final int FULL_NAME = 0; + public static final int NAME_RELATIVE_TO_CRL_ISSUER = 1; + + public static DistributionPointName getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1TaggedObject.getInstance(obj, true)); + } + + public static DistributionPointName getInstance( + Object obj) + { + if (obj == null || obj instanceof DistributionPointName) + { + return (DistributionPointName)obj; + } + else if (obj instanceof ASN1TaggedObject) + { + return new DistributionPointName((ASN1TaggedObject)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + /* + * @deprecated use ASN1Encodable + */ + public DistributionPointName( + int type, + DEREncodable name) + { + this.type = type; + this.name = name; + } + + public DistributionPointName( + int type, + ASN1Encodable name) + { + this.type = type; + this.name = name; + } + + public DistributionPointName( + GeneralNames name) + { + this(FULL_NAME, name); + } + + /** + * Return the tag number applying to the underlying choice. + * + * @return the tag number for this point name. + */ + public int getType() + { + return this.type; + } + + /** + * Return the tagged object inside the distribution point name. + * + * @return the underlying choice item. + */ + public ASN1Encodable getName() + { + return (ASN1Encodable)name; + } + + public DistributionPointName( + ASN1TaggedObject obj) + { + this.type = obj.getTagNo(); + + if (type == 0) + { + this.name = GeneralNames.getInstance(obj, false); + } + else + { + this.name = ASN1Set.getInstance(obj, false); + } + } + + public DERObject toASN1Object() + { + return new DERTaggedObject(false, type, name); + } + + public String toString() + { + String sep = System.getProperty("line.separator"); + StringBuffer buf = new StringBuffer(); + buf.append("DistributionPointName: ["); + buf.append(sep); + if (type == FULL_NAME) + { + appendObject(buf, sep, "fullName", name.toString()); + } + else + { + appendObject(buf, sep, "nameRelativeToCRLIssuer", name.toString()); + } + buf.append("]"); + buf.append(sep); + return buf.toString(); + } + + private void appendObject(StringBuffer buf, String sep, String name, String value) + { + String indent = " "; + + buf.append(indent); + buf.append(name); + buf.append(":"); + buf.append(sep); + buf.append(indent); + buf.append(indent); + buf.append(value); + buf.append(sep); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/ExtendedKeyUsage.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/ExtendedKeyUsage.java new file mode 100644 index 000000000..9e1e3fa32 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/ExtendedKeyUsage.java @@ -0,0 +1,128 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +/** + * The extendedKeyUsage object. + *
+ *      extendedKeyUsage ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
+ * 
+ */ +public class ExtendedKeyUsage + extends ASN1Encodable +{ + Hashtable usageTable = new Hashtable(); + ASN1Sequence seq; + + public static ExtendedKeyUsage getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static ExtendedKeyUsage getInstance( + Object obj) + { + if (obj instanceof ExtendedKeyUsage) + { + return (ExtendedKeyUsage)obj; + } + + if(obj instanceof ASN1Sequence) + { + return new ExtendedKeyUsage((ASN1Sequence)obj); + } + + if (obj instanceof X509Extension) + { + return getInstance(X509Extension.convertValueToObject((X509Extension)obj)); + } + + throw new IllegalArgumentException("Invalid ExtendedKeyUsage: " + obj.getClass().getName()); + } + + public ExtendedKeyUsage( + KeyPurposeId usage) + { + this.seq = new DERSequence(usage); + + this.usageTable.put(usage, usage); + } + + public ExtendedKeyUsage( + ASN1Sequence seq) + { + this.seq = seq; + + Enumeration e = seq.getObjects(); + + while (e.hasMoreElements()) + { + Object o = e.nextElement(); + if (!(o instanceof DERObjectIdentifier)) + { + throw new IllegalArgumentException("Only DERObjectIdentifiers allowed in ExtendedKeyUsage."); + } + this.usageTable.put(o, o); + } + } + + public ExtendedKeyUsage( + Vector usages) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + Enumeration e = usages.elements(); + + while (e.hasMoreElements()) + { + DERObject o = (DERObject)e.nextElement(); + + v.add(o); + this.usageTable.put(o, o); + } + + this.seq = new DERSequence(v); + } + + public boolean hasKeyPurposeId( + KeyPurposeId keyPurposeId) + { + return (usageTable.get(keyPurposeId) != null); + } + + /** + * Returns all extended key usages. + * The returned vector contains DERObjectIdentifiers. + * @return A vector with all key purposes. + */ + public Vector getUsages() + { + Vector temp = new Vector(); + for (Enumeration it = usageTable.elements(); it.hasMoreElements();) + { + temp.addElement(it.nextElement()); + } + return temp; + } + + public int size() + { + return usageTable.size(); + } + + public DERObject toASN1Object() + { + return seq; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/GeneralName.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/GeneralName.java new file mode 100644 index 000000000..28d626399 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/GeneralName.java @@ -0,0 +1,424 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import java.util.StringTokenizer; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERIA5String; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.util.IPAddress; + +/** + * The GeneralName object. + *
+ * GeneralName ::= CHOICE {
+ *      otherName                       [0]     OtherName,
+ *      rfc822Name                      [1]     IA5String,
+ *      dNSName                         [2]     IA5String,
+ *      x400Address                     [3]     ORAddress,
+ *      directoryName                   [4]     Name,
+ *      ediPartyName                    [5]     EDIPartyName,
+ *      uniformResourceIdentifier       [6]     IA5String,
+ *      iPAddress                       [7]     OCTET STRING,
+ *      registeredID                    [8]     OBJECT IDENTIFIER}
+ *
+ * OtherName ::= SEQUENCE {
+ *      type-id    OBJECT IDENTIFIER,
+ *      value      [0] EXPLICIT ANY DEFINED BY type-id }
+ *
+ * EDIPartyName ::= SEQUENCE {
+ *      nameAssigner            [0]     DirectoryString OPTIONAL,
+ *      partyName               [1]     DirectoryString }
+ * 
+ * Name ::= CHOICE { RDNSequence }
+ * 
+ */ +public class GeneralName + extends ASN1Encodable + implements ASN1Choice +{ + public static final int otherName = 0; + public static final int rfc822Name = 1; + public static final int dNSName = 2; + public static final int x400Address = 3; + public static final int directoryName = 4; + public static final int ediPartyName = 5; + public static final int uniformResourceIdentifier = 6; + public static final int iPAddress = 7; + public static final int registeredID = 8; + + DEREncodable obj; + int tag; + + public GeneralName( + X509Name dirName) + { + this.obj = dirName; + this.tag = 4; + } + + /** + * @deprecated this constructor seems the wrong way round! Use GeneralName(tag, name). + */ + public GeneralName( + DERObject name, int tag) + { + this.obj = name; + this.tag = tag; + } + + /** + * When the subjectAltName extension contains an Internet mail address, + * the address MUST be included as an rfc822Name. The format of an + * rfc822Name is an "addr-spec" as defined in RFC 822 [RFC 822]. + * + * When the subjectAltName extension contains a domain name service + * label, the domain name MUST be stored in the dNSName (an IA5String). + * The name MUST be in the "preferred name syntax," as specified by RFC + * 1034 [RFC 1034]. + * + * When the subjectAltName extension contains a URI, the name MUST be + * stored in the uniformResourceIdentifier (an IA5String). The name MUST + * be a non-relative URL, and MUST follow the URL syntax and encoding + * rules specified in [RFC 1738]. The name must include both a scheme + * (e.g., "http" or "ftp") and a scheme-specific-part. The scheme- + * specific-part must include a fully qualified domain name or IP + * address as the host. + * + * When the subjectAltName extension contains a iPAddress, the address + * MUST be stored in the octet string in "network byte order," as + * specified in RFC 791 [RFC 791]. The least significant bit (LSB) of + * each octet is the LSB of the corresponding byte in the network + * address. For IP Version 4, as specified in RFC 791, the octet string + * MUST contain exactly four octets. For IP Version 6, as specified in + * RFC 1883, the octet string MUST contain exactly sixteen octets [RFC + * 1883]. + */ + public GeneralName( + int tag, + ASN1Encodable name) + { + this.obj = name; + this.tag = tag; + } + + /** + * Create a GeneralName for the given tag from the passed in String. + *

+ * This constructor can handle: + *

    + *
  • rfc822Name + *
  • iPAddress + *
  • directoryName + *
  • dNSName + *
  • uniformResourceIdentifier + *
  • registeredID + *
+ * For x400Address, otherName and ediPartyName there is no common string + * format defined. + *

+ * Note: A directory name can be encoded in different ways into a byte + * representation. Be aware of this if the byte representation is used for + * comparing results. + * + * @param tag tag number + * @param name string representation of name + * @throws IllegalArgumentException if the string encoding is not correct or * not supported. + */ + public GeneralName( + int tag, + String name) + { + this.tag = tag; + + if (tag == rfc822Name || tag == dNSName || tag == uniformResourceIdentifier) + { + this.obj = new DERIA5String(name); + } + else if (tag == registeredID) + { + this.obj = new DERObjectIdentifier(name); + } + else if (tag == directoryName) + { + this.obj = new X509Name(name); + } + else if (tag == iPAddress) + { + byte[] enc = toGeneralNameEncoding(name); + if (enc != null) + { + this.obj = new DEROctetString(enc); + } + else + { + throw new IllegalArgumentException("IP Address is invalid"); + } + } + else + { + throw new IllegalArgumentException("can't process String for tag: " + tag); + } + } + + public static GeneralName getInstance( + Object obj) + { + if (obj == null || obj instanceof GeneralName) + { + return (GeneralName)obj; + } + + if (obj instanceof ASN1TaggedObject) + { + ASN1TaggedObject tagObj = (ASN1TaggedObject)obj; + int tag = tagObj.getTagNo(); + + switch (tag) + { + case otherName: + return new GeneralName(tag, ASN1Sequence.getInstance(tagObj, false)); + case rfc822Name: + return new GeneralName(tag, DERIA5String.getInstance(tagObj, false)); + case dNSName: + return new GeneralName(tag, DERIA5String.getInstance(tagObj, false)); + case x400Address: + throw new IllegalArgumentException("unknown tag: " + tag); + case directoryName: + return new GeneralName(tag, X509Name.getInstance(tagObj, true)); + case ediPartyName: + return new GeneralName(tag, ASN1Sequence.getInstance(tagObj, false)); + case uniformResourceIdentifier: + return new GeneralName(tag, DERIA5String.getInstance(tagObj, false)); + case iPAddress: + return new GeneralName(tag, ASN1OctetString.getInstance(tagObj, false)); + case registeredID: + return new GeneralName(tag, DERObjectIdentifier.getInstance(tagObj, false)); + } + } + + throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName()); + } + + public static GeneralName getInstance( + ASN1TaggedObject tagObj, + boolean explicit) + { + return GeneralName.getInstance(ASN1TaggedObject.getInstance(tagObj, true)); + } + + public int getTagNo() + { + return tag; + } + + public DEREncodable getName() + { + return obj; + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + + buf.append(tag); + buf.append(": "); + switch (tag) + { + case rfc822Name: + case dNSName: + case uniformResourceIdentifier: + buf.append(DERIA5String.getInstance(obj).getString()); + break; + case directoryName: + buf.append(X509Name.getInstance(obj).toString()); + break; + default: + buf.append(obj.toString()); + } + return buf.toString(); + } + + private byte[] toGeneralNameEncoding(String ip) + { + if (IPAddress.isValidIPv6WithNetmask(ip) || IPAddress.isValidIPv6(ip)) + { + int slashIndex = ip.indexOf('/'); + + if (slashIndex < 0) + { + byte[] addr = new byte[16]; + int[] parsedIp = parseIPv6(ip); + copyInts(parsedIp, addr, 0); + + return addr; + } + else + { + byte[] addr = new byte[32]; + int[] parsedIp = parseIPv6(ip.substring(0, slashIndex)); + copyInts(parsedIp, addr, 0); + String mask = ip.substring(slashIndex + 1); + if (mask.indexOf(':') > 0) + { + parsedIp = parseIPv6(mask); + } + else + { + parsedIp = parseMask(mask); + } + copyInts(parsedIp, addr, 16); + + return addr; + } + } + else if (IPAddress.isValidIPv4WithNetmask(ip) || IPAddress.isValidIPv4(ip)) + { + int slashIndex = ip.indexOf('/'); + + if (slashIndex < 0) + { + byte[] addr = new byte[4]; + + parseIPv4(ip, addr, 0); + + return addr; + } + else + { + byte[] addr = new byte[8]; + + parseIPv4(ip.substring(0, slashIndex), addr, 0); + + String mask = ip.substring(slashIndex + 1); + if (mask.indexOf('.') > 0) + { + parseIPv4(mask, addr, 4); + } + else + { + parseIPv4Mask(mask, addr, 4); + } + + return addr; + } + } + + return null; + } + + private void parseIPv4Mask(String mask, byte[] addr, int offset) + { + int maskVal = Integer.parseInt(mask); + + for (int i = 0; i != maskVal; i++) + { + addr[(i / 8) + offset] |= 1 << (i % 8); + } + } + + private void parseIPv4(String ip, byte[] addr, int offset) + { + StringTokenizer sTok = new StringTokenizer(ip, "./"); + int index = 0; + + while (sTok.hasMoreTokens()) + { + addr[offset + index++] = (byte)Integer.parseInt(sTok.nextToken()); + } + } + + private int[] parseMask(String mask) + { + int[] res = new int[8]; + int maskVal = Integer.parseInt(mask); + + for (int i = 0; i != maskVal; i++) + { + res[i / 16] |= 1 << (i % 16); + } + return res; + } + + private void copyInts(int[] parsedIp, byte[] addr, int offSet) + { + for (int i = 0; i != parsedIp.length; i++) + { + addr[(i * 2) + offSet] = (byte)(parsedIp[i] >> 8); + addr[(i * 2 + 1) + offSet] = (byte)parsedIp[i]; + } + } + + private int[] parseIPv6(String ip) + { + StringTokenizer sTok = new StringTokenizer(ip, ":", true); + int index = 0; + int[] val = new int[8]; + + if (ip.charAt(0) == ':' && ip.charAt(1) == ':') + { + sTok.nextToken(); // skip the first one + } + + int doubleColon = -1; + + while (sTok.hasMoreTokens()) + { + String e = sTok.nextToken(); + + if (e.equals(":")) + { + doubleColon = index; + val[index++] = 0; + } + else + { + if (e.indexOf('.') < 0) + { + val[index++] = Integer.parseInt(e, 16); + if (sTok.hasMoreTokens()) + { + sTok.nextToken(); + } + } + else + { + StringTokenizer eTok = new StringTokenizer(e, "."); + + val[index++] = (Integer.parseInt(eTok.nextToken()) << 8) | Integer.parseInt(eTok.nextToken()); + val[index++] = (Integer.parseInt(eTok.nextToken()) << 8) | Integer.parseInt(eTok.nextToken()); + } + } + } + + if (index != val.length) + { + System.arraycopy(val, doubleColon, val, val.length - (index - doubleColon), index - doubleColon); + for (int i = doubleColon; i != val.length - (index - doubleColon); i++) + { + val[i] = 0; + } + } + + return val; + } + + public DERObject toASN1Object() + { + if (tag == directoryName) // directoryName is explicitly tagged as it is a CHOICE + { + return new DERTaggedObject(true, tag, obj); + } + else + { + return new DERTaggedObject(false, tag, obj); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/GeneralNames.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/GeneralNames.java new file mode 100644 index 000000000..6ceaa77d0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/GeneralNames.java @@ -0,0 +1,95 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class GeneralNames + extends ASN1Encodable +{ + private final GeneralName[] names; + + public static GeneralNames getInstance( + Object obj) + { + if (obj == null || obj instanceof GeneralNames) + { + return (GeneralNames)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new GeneralNames((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + public static GeneralNames getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * Construct a GeneralNames object containing one GeneralName. + * + * @param name the name to be contained. + */ + public GeneralNames( + GeneralName name) + { + this.names = new GeneralName[] { name }; + } + + public GeneralNames( + ASN1Sequence seq) + { + this.names = new GeneralName[seq.size()]; + + for (int i = 0; i != seq.size(); i++) + { + names[i] = GeneralName.getInstance(seq.getObjectAt(i)); + } + } + + public GeneralName[] getNames() + { + GeneralName[] tmp = new GeneralName[names.length]; + + System.arraycopy(names, 0, tmp, 0, names.length); + + return tmp; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+     * GeneralNames ::= SEQUENCE SIZE {1..MAX} OF GeneralName
+     * 
+ */ + public DERObject toASN1Object() + { + return new DERSequence(names); + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + String sep = System.getProperty("line.separator"); + + buf.append("GeneralNames:"); + buf.append(sep); + + for (int i = 0; i != names.length; i++) + { + buf.append(" "); + buf.append(names[i]); + buf.append(sep); + } + return buf.toString(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/GeneralSubtree.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/GeneralSubtree.java new file mode 100644 index 000000000..f1f64e4d7 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/GeneralSubtree.java @@ -0,0 +1,200 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +import java.math.BigInteger; + +/** + * Class for containing a restriction object subtrees in NameConstraints. See + * RFC 3280. + * + *
+ *       
+ *       GeneralSubtree ::= SEQUENCE 
+ *       {
+ *         base                    GeneralName,
+ *         minimum         [0]     BaseDistance DEFAULT 0,
+ *         maximum         [1]     BaseDistance OPTIONAL 
+ *       }
+ * 
+ * + * @see com.google.bitcoin.bouncycastle.asn1.x509.NameConstraints + * + */ +public class GeneralSubtree + extends ASN1Encodable +{ + private static final BigInteger ZERO = BigInteger.valueOf(0); + + private GeneralName base; + + private DERInteger minimum; + + private DERInteger maximum; + + public GeneralSubtree( + ASN1Sequence seq) + { + base = GeneralName.getInstance(seq.getObjectAt(0)); + + switch (seq.size()) + { + case 1: + break; + case 2: + ASN1TaggedObject o = ASN1TaggedObject.getInstance(seq.getObjectAt(1)); + switch (o.getTagNo()) + { + case 0: + minimum = DERInteger.getInstance(o, false); + break; + case 1: + maximum = DERInteger.getInstance(o, false); + break; + default: + throw new IllegalArgumentException("Bad tag number: " + + o.getTagNo()); + } + break; + case 3: + minimum = DERInteger.getInstance(ASN1TaggedObject.getInstance(seq.getObjectAt(1))); + maximum = DERInteger.getInstance(ASN1TaggedObject.getInstance(seq.getObjectAt(2))); + break; + default: + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + } + + /** + * Constructor from a given details. + * + * According RFC 3280, the minimum and maximum fields are not used with any + * name forms, thus minimum MUST be zero, and maximum MUST be absent. + *

+ * If minimum is null, zero is assumed, if + * maximum is null, maximum is absent. + * + * @param base + * A restriction. + * @param minimum + * Minimum + * + * @param maximum + * Maximum + */ + public GeneralSubtree( + GeneralName base, + BigInteger minimum, + BigInteger maximum) + { + this.base = base; + if (maximum != null) + { + this.maximum = new DERInteger(maximum); + } + if (minimum == null) + { + this.minimum = null; + } + else + { + this.minimum = new DERInteger(minimum); + } + } + + public GeneralSubtree(GeneralName base) + { + this(base, null, null); + } + + public static GeneralSubtree getInstance( + ASN1TaggedObject o, + boolean explicit) + { + return new GeneralSubtree(ASN1Sequence.getInstance(o, explicit)); + } + + public static GeneralSubtree getInstance( + Object obj) + { + if (obj == null) + { + return null; + } + + if (obj instanceof GeneralSubtree) + { + return (GeneralSubtree) obj; + } + + return new GeneralSubtree(ASN1Sequence.getInstance(obj)); + } + + public GeneralName getBase() + { + return base; + } + + public BigInteger getMinimum() + { + if (minimum == null) + { + return ZERO; + } + + return minimum.getValue(); + } + + public BigInteger getMaximum() + { + if (maximum == null) + { + return null; + } + + return maximum.getValue(); + } + + /** + * Produce an object suitable for an ASN1OutputStream. + * + * Returns: + * + *

+     *       GeneralSubtree ::= SEQUENCE 
+     *       {
+     *         base                    GeneralName,
+     *         minimum         [0]     BaseDistance DEFAULT 0,
+     *         maximum         [1]     BaseDistance OPTIONAL 
+     *       }
+     * 
+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(base); + + if (minimum != null && !minimum.getValue().equals(ZERO)) + { + v.add(new DERTaggedObject(false, 0, minimum)); + } + + if (maximum != null) + { + v.add(new DERTaggedObject(false, 1, maximum)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/Holder.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/Holder.java new file mode 100644 index 000000000..e1fc2784a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/Holder.java @@ -0,0 +1,242 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +/** + * The Holder object. + *

+ * For an v2 attribute certificate this is: + * + *

+ *            Holder ::= SEQUENCE {
+ *                  baseCertificateID   [0] IssuerSerial OPTIONAL,
+ *                           -- the issuer and serial number of
+ *                           -- the holder's Public Key Certificate
+ *                  entityName          [1] GeneralNames OPTIONAL,
+ *                           -- the name of the claimant or role
+ *                  objectDigestInfo    [2] ObjectDigestInfo OPTIONAL
+ *                           -- used to directly authenticate the holder,
+ *                           -- for example, an executable
+ *            }
+ * 
+ * + *

+ * For an v1 attribute certificate this is: + * + *

+ *         subject CHOICE {
+ *          baseCertificateID [0] IssuerSerial,
+ *          -- associated with a Public Key Certificate
+ *          subjectName [1] GeneralNames },
+ *          -- associated with a name
+ * 
+ */ +public class Holder + extends ASN1Encodable +{ + IssuerSerial baseCertificateID; + + GeneralNames entityName; + + ObjectDigestInfo objectDigestInfo; + + private int version = 1; + + public static Holder getInstance(Object obj) + { + if (obj instanceof Holder) + { + return (Holder)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new Holder((ASN1Sequence)obj); + } + else if (obj instanceof ASN1TaggedObject) + { + return new Holder((ASN1TaggedObject)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + /** + * Constructor for a holder for an v1 attribute certificate. + * + * @param tagObj The ASN.1 tagged holder object. + */ + public Holder(ASN1TaggedObject tagObj) + { + switch (tagObj.getTagNo()) + { + case 0: + baseCertificateID = IssuerSerial.getInstance(tagObj, false); + break; + case 1: + entityName = GeneralNames.getInstance(tagObj, false); + break; + default: + throw new IllegalArgumentException("unknown tag in Holder"); + } + version = 0; + } + + /** + * Constructor for a holder for an v2 attribute certificate. * + * + * @param seq The ASN.1 sequence. + */ + public Holder(ASN1Sequence seq) + { + if (seq.size() > 3) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + for (int i = 0; i != seq.size(); i++) + { + ASN1TaggedObject tObj = ASN1TaggedObject.getInstance(seq + .getObjectAt(i)); + + switch (tObj.getTagNo()) + { + case 0: + baseCertificateID = IssuerSerial.getInstance(tObj, false); + break; + case 1: + entityName = GeneralNames.getInstance(tObj, false); + break; + case 2: + objectDigestInfo = ObjectDigestInfo.getInstance(tObj, false); + break; + default: + throw new IllegalArgumentException("unknown tag in Holder"); + } + } + version = 1; + } + + public Holder(IssuerSerial baseCertificateID) + { + this.baseCertificateID = baseCertificateID; + } + + /** + * Constructs a holder from a IssuerSerial. + * @param baseCertificateID The IssuerSerial. + * @param version The version of the attribute certificate. + */ + public Holder(IssuerSerial baseCertificateID, int version) + { + this.baseCertificateID = baseCertificateID; + this.version = version; + } + + /** + * Returns 1 for v2 attribute certificates or 0 for v1 attribute + * certificates. + * @return The version of the attribute certificate. + */ + public int getVersion() + { + return version; + } + + /** + * Constructs a holder with an entityName for v2 attribute certificates or + * with a subjectName for v1 attribute certificates. + * + * @param entityName The entity or subject name. + */ + public Holder(GeneralNames entityName) + { + this.entityName = entityName; + } + + /** + * Constructs a holder with an entityName for v2 attribute certificates or + * with a subjectName for v1 attribute certificates. + * + * @param entityName The entity or subject name. + * @param version The version of the attribute certificate. + */ + public Holder(GeneralNames entityName, int version) + { + this.entityName = entityName; + this.version = version; + } + + /** + * Constructs a holder from an object digest info. + * + * @param objectDigestInfo The object digest info object. + */ + public Holder(ObjectDigestInfo objectDigestInfo) + { + this.objectDigestInfo = objectDigestInfo; + } + + public IssuerSerial getBaseCertificateID() + { + return baseCertificateID; + } + + /** + * Returns the entityName for an v2 attribute certificate or the subjectName + * for an v1 attribute certificate. + * + * @return The entityname or subjectname. + */ + public GeneralNames getEntityName() + { + return entityName; + } + + public ObjectDigestInfo getObjectDigestInfo() + { + return objectDigestInfo; + } + + public DERObject toASN1Object() + { + if (version == 1) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (baseCertificateID != null) + { + v.add(new DERTaggedObject(false, 0, baseCertificateID)); + } + + if (entityName != null) + { + v.add(new DERTaggedObject(false, 1, entityName)); + } + + if (objectDigestInfo != null) + { + v.add(new DERTaggedObject(false, 2, objectDigestInfo)); + } + + return new DERSequence(v); + } + else + { + if (entityName != null) + { + return new DERTaggedObject(false, 1, entityName); + } + else + { + return new DERTaggedObject(false, 0, baseCertificateID); + } + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/IetfAttrSyntax.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/IetfAttrSyntax.java new file mode 100644 index 000000000..8470cd8fa --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/IetfAttrSyntax.java @@ -0,0 +1,174 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import java.util.Enumeration; +import java.util.Vector; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERUTF8String; + +/** + * Implementation of IetfAttrSyntax as specified by RFC3281. + */ +public class IetfAttrSyntax + extends ASN1Encodable +{ + public static final int VALUE_OCTETS = 1; + public static final int VALUE_OID = 2; + public static final int VALUE_UTF8 = 3; + GeneralNames policyAuthority = null; + Vector values = new Vector(); + int valueChoice = -1; + + /** + * + */ + public IetfAttrSyntax(ASN1Sequence seq) + { + int i = 0; + + if (seq.getObjectAt(0) instanceof ASN1TaggedObject) + { + policyAuthority = GeneralNames.getInstance(((ASN1TaggedObject)seq.getObjectAt(0)), false); + i++; + } + else if (seq.size() == 2) + { // VOMS fix + policyAuthority = GeneralNames.getInstance(seq.getObjectAt(0)); + i++; + } + + if (!(seq.getObjectAt(i) instanceof ASN1Sequence)) + { + throw new IllegalArgumentException("Non-IetfAttrSyntax encoding"); + } + + seq = (ASN1Sequence)seq.getObjectAt(i); + + for (Enumeration e = seq.getObjects(); e.hasMoreElements();) + { + DERObject obj = (DERObject)e.nextElement(); + int type; + + if (obj instanceof DERObjectIdentifier) + { + type = VALUE_OID; + } + else if (obj instanceof DERUTF8String) + { + type = VALUE_UTF8; + } + else if (obj instanceof DEROctetString) + { + type = VALUE_OCTETS; + } + else + { + throw new IllegalArgumentException("Bad value type encoding IetfAttrSyntax"); + } + + if (valueChoice < 0) + { + valueChoice = type; + } + + if (type != valueChoice) + { + throw new IllegalArgumentException("Mix of value types in IetfAttrSyntax"); + } + + values.addElement(obj); + } + } + + public GeneralNames getPolicyAuthority() + { + return policyAuthority; + } + + public int getValueType() + { + return valueChoice; + } + + public Object[] getValues() + { + if (this.getValueType() == VALUE_OCTETS) + { + ASN1OctetString[] tmp = new ASN1OctetString[values.size()]; + + for (int i = 0; i != tmp.length; i++) + { + tmp[i] = (ASN1OctetString)values.elementAt(i); + } + + return tmp; + } + else if (this.getValueType() == VALUE_OID) + { + DERObjectIdentifier[] tmp = new DERObjectIdentifier[values.size()]; + + for (int i = 0; i != tmp.length; i++) + { + tmp[i] = (DERObjectIdentifier)values.elementAt(i); + } + + return tmp; + } + else + { + DERUTF8String[] tmp = new DERUTF8String[values.size()]; + + for (int i = 0; i != tmp.length; i++) + { + tmp[i] = (DERUTF8String)values.elementAt(i); + } + + return tmp; + } + } + + /** + * + *
+     * 
+     *  IetfAttrSyntax ::= SEQUENCE {
+     *    policyAuthority [0] GeneralNames OPTIONAL,
+     *    values SEQUENCE OF CHOICE {
+     *      octets OCTET STRING,
+     *      oid OBJECT IDENTIFIER,
+     *      string UTF8String
+     *    }
+     *  }
+     *  
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (policyAuthority != null) + { + v.add(new DERTaggedObject(0, policyAuthority)); + } + + ASN1EncodableVector v2 = new ASN1EncodableVector(); + + for (Enumeration i = values.elements(); i.hasMoreElements();) + { + v2.add((ASN1Encodable)i.nextElement()); + } + + v.add(new DERSequence(v2)); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/IssuerSerial.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/IssuerSerial.java new file mode 100644 index 000000000..c3310e7ef --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/IssuerSerial.java @@ -0,0 +1,106 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class IssuerSerial + extends ASN1Encodable +{ + GeneralNames issuer; + DERInteger serial; + DERBitString issuerUID; + + public static IssuerSerial getInstance( + Object obj) + { + if (obj == null || obj instanceof IssuerSerial) + { + return (IssuerSerial)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new IssuerSerial((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + public static IssuerSerial getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public IssuerSerial( + ASN1Sequence seq) + { + if (seq.size() != 2 && seq.size() != 3) + { + throw new IllegalArgumentException("Bad sequence size: " + seq.size()); + } + + issuer = GeneralNames.getInstance(seq.getObjectAt(0)); + serial = DERInteger.getInstance(seq.getObjectAt(1)); + + if (seq.size() == 3) + { + issuerUID = DERBitString.getInstance(seq.getObjectAt(2)); + } + } + + public IssuerSerial( + GeneralNames issuer, + DERInteger serial) + { + this.issuer = issuer; + this.serial = serial; + } + + public GeneralNames getIssuer() + { + return issuer; + } + + public DERInteger getSerial() + { + return serial; + } + + public DERBitString getIssuerUID() + { + return issuerUID; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  IssuerSerial  ::=  SEQUENCE {
+     *       issuer         GeneralNames,
+     *       serial         CertificateSerialNumber,
+     *       issuerUID      UniqueIdentifier OPTIONAL
+     *  }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(issuer); + v.add(serial); + + if (issuerUID != null) + { + v.add(issuerUID); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/IssuingDistributionPoint.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/IssuingDistributionPoint.java new file mode 100644 index 000000000..5db241218 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/IssuingDistributionPoint.java @@ -0,0 +1,256 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBoolean; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +/** + *
+ * IssuingDistributionPoint ::= SEQUENCE { 
+ *   distributionPoint          [0] DistributionPointName OPTIONAL, 
+ *   onlyContainsUserCerts      [1] BOOLEAN DEFAULT FALSE, 
+ *   onlyContainsCACerts        [2] BOOLEAN DEFAULT FALSE, 
+ *   onlySomeReasons            [3] ReasonFlags OPTIONAL, 
+ *   indirectCRL                [4] BOOLEAN DEFAULT FALSE,
+ *   onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE }
+ * 
+ */ +public class IssuingDistributionPoint + extends ASN1Encodable +{ + private DistributionPointName distributionPoint; + + private boolean onlyContainsUserCerts; + + private boolean onlyContainsCACerts; + + private ReasonFlags onlySomeReasons; + + private boolean indirectCRL; + + private boolean onlyContainsAttributeCerts; + + private ASN1Sequence seq; + + public static IssuingDistributionPoint getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static IssuingDistributionPoint getInstance( + Object obj) + { + if (obj == null || obj instanceof IssuingDistributionPoint) + { + return (IssuingDistributionPoint)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new IssuingDistributionPoint((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + /** + * Constructor from given details. + * + * @param distributionPoint + * May contain an URI as pointer to most current CRL. + * @param onlyContainsUserCerts Covers revocation information for end certificates. + * @param onlyContainsCACerts Covers revocation information for CA certificates. + * + * @param onlySomeReasons + * Which revocation reasons does this point cover. + * @param indirectCRL + * If true then the CRL contains revocation + * information about certificates ssued by other CAs. + * @param onlyContainsAttributeCerts Covers revocation information for attribute certificates. + */ + public IssuingDistributionPoint( + DistributionPointName distributionPoint, + boolean onlyContainsUserCerts, + boolean onlyContainsCACerts, + ReasonFlags onlySomeReasons, + boolean indirectCRL, + boolean onlyContainsAttributeCerts) + { + this.distributionPoint = distributionPoint; + this.indirectCRL = indirectCRL; + this.onlyContainsAttributeCerts = onlyContainsAttributeCerts; + this.onlyContainsCACerts = onlyContainsCACerts; + this.onlyContainsUserCerts = onlyContainsUserCerts; + this.onlySomeReasons = onlySomeReasons; + + ASN1EncodableVector vec = new ASN1EncodableVector(); + if (distributionPoint != null) + { // CHOICE item so explicitly tagged + vec.add(new DERTaggedObject(true, 0, distributionPoint)); + } + if (onlyContainsUserCerts) + { + vec.add(new DERTaggedObject(false, 1, new DERBoolean(true))); + } + if (onlyContainsCACerts) + { + vec.add(new DERTaggedObject(false, 2, new DERBoolean(true))); + } + if (onlySomeReasons != null) + { + vec.add(new DERTaggedObject(false, 3, onlySomeReasons)); + } + if (indirectCRL) + { + vec.add(new DERTaggedObject(false, 4, new DERBoolean(true))); + } + if (onlyContainsAttributeCerts) + { + vec.add(new DERTaggedObject(false, 5, new DERBoolean(true))); + } + + seq = new DERSequence(vec); + } + + /** + * Constructor from ASN1Sequence + */ + public IssuingDistributionPoint( + ASN1Sequence seq) + { + this.seq = seq; + + for (int i = 0; i != seq.size(); i++) + { + ASN1TaggedObject o = ASN1TaggedObject.getInstance(seq.getObjectAt(i)); + + switch (o.getTagNo()) + { + case 0: + // CHOICE so explicit + distributionPoint = DistributionPointName.getInstance(o, true); + break; + case 1: + onlyContainsUserCerts = DERBoolean.getInstance(o, false).isTrue(); + break; + case 2: + onlyContainsCACerts = DERBoolean.getInstance(o, false).isTrue(); + break; + case 3: + onlySomeReasons = new ReasonFlags(ReasonFlags.getInstance(o, false)); + break; + case 4: + indirectCRL = DERBoolean.getInstance(o, false).isTrue(); + break; + case 5: + onlyContainsAttributeCerts = DERBoolean.getInstance(o, false).isTrue(); + break; + default: + throw new IllegalArgumentException( + "unknown tag in IssuingDistributionPoint"); + } + } + } + + public boolean onlyContainsUserCerts() + { + return onlyContainsUserCerts; + } + + public boolean onlyContainsCACerts() + { + return onlyContainsCACerts; + } + + public boolean isIndirectCRL() + { + return indirectCRL; + } + + public boolean onlyContainsAttributeCerts() + { + return onlyContainsAttributeCerts; + } + + /** + * @return Returns the distributionPoint. + */ + public DistributionPointName getDistributionPoint() + { + return distributionPoint; + } + + /** + * @return Returns the onlySomeReasons. + */ + public ReasonFlags getOnlySomeReasons() + { + return onlySomeReasons; + } + + public DERObject toASN1Object() + { + return seq; + } + + public String toString() + { + String sep = System.getProperty("line.separator"); + StringBuffer buf = new StringBuffer(); + + buf.append("IssuingDistributionPoint: ["); + buf.append(sep); + if (distributionPoint != null) + { + appendObject(buf, sep, "distributionPoint", distributionPoint.toString()); + } + if (onlyContainsUserCerts) + { + appendObject(buf, sep, "onlyContainsUserCerts", booleanToString(onlyContainsUserCerts)); + } + if (onlyContainsCACerts) + { + appendObject(buf, sep, "onlyContainsCACerts", booleanToString(onlyContainsCACerts)); + } + if (onlySomeReasons != null) + { + appendObject(buf, sep, "onlySomeReasons", onlySomeReasons.toString()); + } + if (onlyContainsAttributeCerts) + { + appendObject(buf, sep, "onlyContainsAttributeCerts", booleanToString(onlyContainsAttributeCerts)); + } + if (indirectCRL) + { + appendObject(buf, sep, "indirectCRL", booleanToString(indirectCRL)); + } + buf.append("]"); + buf.append(sep); + return buf.toString(); + } + + private void appendObject(StringBuffer buf, String sep, String name, String value) + { + String indent = " "; + + buf.append(indent); + buf.append(name); + buf.append(":"); + buf.append(sep); + buf.append(indent); + buf.append(indent); + buf.append(value); + buf.append(sep); + } + + private String booleanToString(boolean value) + { + return value ? "true" : "false"; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/KeyPurposeId.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/KeyPurposeId.java new file mode 100644 index 000000000..28e3daaba --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/KeyPurposeId.java @@ -0,0 +1,119 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +/** + * The KeyPurposeId object. + *
+ *     KeyPurposeId ::= OBJECT IDENTIFIER
+ *
+ *     id-kp ::= OBJECT IDENTIFIER { iso(1) identified-organization(3) 
+ *          dod(6) internet(1) security(5) mechanisms(5) pkix(7) 3}
+ *
+ * 
+ */ +public class KeyPurposeId + extends DERObjectIdentifier +{ + private static final String id_kp = "1.3.6.1.5.5.7.3"; + + /** + * Create a KeyPurposeId from an OID string + * + * @param id OID String. E.g. "1.3.6.1.5.5.7.3.1" + */ + public KeyPurposeId( + String id) + { + super(id); + } + + /** + * { 2 5 29 37 0 } + */ + public static final KeyPurposeId anyExtendedKeyUsage = new KeyPurposeId(X509Extensions.ExtendedKeyUsage.getId() + ".0"); + /** + * { id-kp 1 } + */ + public static final KeyPurposeId id_kp_serverAuth = new KeyPurposeId(id_kp + ".1"); + /** + * { id-kp 2 } + */ + public static final KeyPurposeId id_kp_clientAuth = new KeyPurposeId(id_kp + ".2"); + /** + * { id-kp 3 } + */ + public static final KeyPurposeId id_kp_codeSigning = new KeyPurposeId(id_kp + ".3"); + /** + * { id-kp 4 } + */ + public static final KeyPurposeId id_kp_emailProtection = new KeyPurposeId(id_kp + ".4"); + /** + * Usage deprecated by RFC4945 - was { id-kp 5 } + */ + public static final KeyPurposeId id_kp_ipsecEndSystem = new KeyPurposeId(id_kp + ".5"); + /** + * Usage deprecated by RFC4945 - was { id-kp 6 } + */ + public static final KeyPurposeId id_kp_ipsecTunnel = new KeyPurposeId(id_kp + ".6"); + /** + * Usage deprecated by RFC4945 - was { idkp 7 } + */ + public static final KeyPurposeId id_kp_ipsecUser = new KeyPurposeId(id_kp + ".7"); + /** + * { id-kp 8 } + */ + public static final KeyPurposeId id_kp_timeStamping = new KeyPurposeId(id_kp + ".8"); + /** + * { id-kp 9 } + */ + public static final KeyPurposeId id_kp_OCSPSigning = new KeyPurposeId(id_kp + ".9"); + /** + * { id-kp 10 } + */ + public static final KeyPurposeId id_kp_dvcs = new KeyPurposeId(id_kp + ".10"); + /** + * { id-kp 11 } + */ + public static final KeyPurposeId id_kp_sbgpCertAAServerAuth = new KeyPurposeId(id_kp + ".11"); + /** + * { id-kp 12 } + */ + public static final KeyPurposeId id_kp_scvp_responder = new KeyPurposeId(id_kp + ".12"); + /** + * { id-kp 13 } + */ + public static final KeyPurposeId id_kp_eapOverPPP = new KeyPurposeId(id_kp + ".13"); + /** + * { id-kp 14 } + */ + public static final KeyPurposeId id_kp_eapOverLAN = new KeyPurposeId(id_kp + ".14"); + /** + * { id-kp 15 } + */ + public static final KeyPurposeId id_kp_scvpServer = new KeyPurposeId(id_kp + ".15"); + /** + * { id-kp 16 } + */ + public static final KeyPurposeId id_kp_scvpClient = new KeyPurposeId(id_kp + ".16"); + /** + * { id-kp 17 } + */ + public static final KeyPurposeId id_kp_ipsecIKE = new KeyPurposeId(id_kp + ".17"); + /** + * { id-kp 18 } + */ + public static final KeyPurposeId id_kp_capwapAC = new KeyPurposeId(id_kp + ".18"); + /** + * { id-kp 19 } + */ + public static final KeyPurposeId id_kp_capwapWTP = new KeyPurposeId(id_kp + ".19"); + + // + // microsoft key purpose ids + // + /** + * { 1 3 6 1 4 1 311 20 2 2 } + */ + public static final KeyPurposeId id_kp_smartcardlogon = new KeyPurposeId("1.3.6.1.4.1.311.20.2.2"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/KeyUsage.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/KeyUsage.java new file mode 100644 index 000000000..15e4ee1c2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/KeyUsage.java @@ -0,0 +1,77 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.DERBitString; + +/** + * The KeyUsage object. + *
+ *    id-ce-keyUsage OBJECT IDENTIFIER ::=  { id-ce 15 }
+ *
+ *    KeyUsage ::= BIT STRING {
+ *         digitalSignature        (0),
+ *         nonRepudiation          (1),
+ *         keyEncipherment         (2),
+ *         dataEncipherment        (3),
+ *         keyAgreement            (4),
+ *         keyCertSign             (5),
+ *         cRLSign                 (6),
+ *         encipherOnly            (7),
+ *         decipherOnly            (8) }
+ * 
+ */ +public class KeyUsage + extends DERBitString +{ + public static final int digitalSignature = (1 << 7); + public static final int nonRepudiation = (1 << 6); + public static final int keyEncipherment = (1 << 5); + public static final int dataEncipherment = (1 << 4); + public static final int keyAgreement = (1 << 3); + public static final int keyCertSign = (1 << 2); + public static final int cRLSign = (1 << 1); + public static final int encipherOnly = (1 << 0); + public static final int decipherOnly = (1 << 15); + + public static DERBitString getInstance(Object obj) // needs to be DERBitString for other VMs + { + if (obj instanceof KeyUsage) + { + return (KeyUsage)obj; + } + + if (obj instanceof X509Extension) + { + return new KeyUsage(DERBitString.getInstance(X509Extension.convertValueToObject((X509Extension)obj))); + } + + return new KeyUsage(DERBitString.getInstance(obj)); + } + + /** + * Basic constructor. + * + * @param usage - the bitwise OR of the Key Usage flags giving the + * allowed uses for the key. + * e.g. (KeyUsage.keyEncipherment | KeyUsage.dataEncipherment) + */ + public KeyUsage( + int usage) + { + super(getBytes(usage), getPadBits(usage)); + } + + public KeyUsage( + DERBitString usage) + { + super(usage.getBytes(), usage.getPadBits()); + } + + public String toString() + { + if (data.length == 1) + { + return "KeyUsage: 0x" + Integer.toHexString(data[0] & 0xff); + } + return "KeyUsage: 0x" + Integer.toHexString((data[1] & 0xff) << 8 | (data[0] & 0xff)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/NameConstraints.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/NameConstraints.java new file mode 100644 index 000000000..e51a28598 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/NameConstraints.java @@ -0,0 +1,104 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import java.util.Enumeration; +import java.util.Vector; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class NameConstraints + extends ASN1Encodable +{ + private ASN1Sequence permitted, excluded; + + public NameConstraints(ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + while (e.hasMoreElements()) + { + ASN1TaggedObject o = ASN1TaggedObject.getInstance(e.nextElement()); + switch (o.getTagNo()) + { + case 0: + permitted = ASN1Sequence.getInstance(o, false); + break; + case 1: + excluded = ASN1Sequence.getInstance(o, false); + break; + } + } + } + + /** + * Constructor from a given details. + * + *

+ * permitted and excluded are Vectors of GeneralSubtree objects. + * + * @param permitted + * Permitted subtrees + * @param excluded + * Excludes subtrees + */ + public NameConstraints( + Vector permitted, + Vector excluded) + { + if (permitted != null) + { + this.permitted = createSequence(permitted); + } + if (excluded != null) + { + this.excluded = createSequence(excluded); + } + } + + private DERSequence createSequence(Vector subtree) + { + ASN1EncodableVector vec = new ASN1EncodableVector(); + Enumeration e = subtree.elements(); + while (e.hasMoreElements()) + { + vec.add((GeneralSubtree)e.nextElement()); + } + + return new DERSequence(vec); + } + + public ASN1Sequence getPermittedSubtrees() + { + return permitted; + } + + public ASN1Sequence getExcludedSubtrees() + { + return excluded; + } + + /* + * NameConstraints ::= SEQUENCE { permittedSubtrees [0] GeneralSubtrees + * OPTIONAL, excludedSubtrees [1] GeneralSubtrees OPTIONAL } + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (permitted != null) + { + v.add(new DERTaggedObject(false, 0, permitted)); + } + + if (excluded != null) + { + v.add(new DERTaggedObject(false, 1, excluded)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/NoticeReference.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/NoticeReference.java new file mode 100644 index 000000000..1beeffe45 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/NoticeReference.java @@ -0,0 +1,155 @@ + +package com.google.bitcoin.bouncycastle.asn1.x509; + +import java.util.Enumeration; +import java.util.Vector; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * NoticeReference class, used in + * CertificatePolicies X509 V3 extensions + * (in policy qualifiers). + * + *

+ *  NoticeReference ::= SEQUENCE {
+ *      organization     DisplayText,
+ *      noticeNumbers    SEQUENCE OF INTEGER }
+ *
+ * 
+ * + * @see PolicyQualifierInfo + * @see PolicyInformation + */ +public class NoticeReference + extends ASN1Encodable +{ + private DisplayText organization; + private ASN1Sequence noticeNumbers; + + /** + * Creates a new NoticeReference instance. + * + * @param orgName a String value + * @param numbers a Vector value + */ + public NoticeReference( + String orgName, + Vector numbers) + { + organization = new DisplayText(orgName); + + Object o = numbers.elementAt(0); + + ASN1EncodableVector av = new ASN1EncodableVector(); + if (o instanceof Integer) + { + Enumeration it = numbers.elements(); + + while (it.hasMoreElements()) + { + Integer nm = (Integer) it.nextElement(); + DERInteger di = new DERInteger(nm.intValue()); + av.add (di); + } + } + + noticeNumbers = new DERSequence(av); + } + + /** + * Creates a new NoticeReference instance. + * + * @param orgName a String value + * @param numbers an ASN1EncodableVector value + */ + public NoticeReference( + String orgName, + ASN1Sequence numbers) + { + organization = new DisplayText (orgName); + noticeNumbers = numbers; + } + + /** + * Creates a new NoticeReference instance. + * + * @param displayTextType an int value + * @param orgName a String value + * @param numbers an ASN1EncodableVector value + */ + public NoticeReference( + int displayTextType, + String orgName, + ASN1Sequence numbers) + { + organization = new DisplayText(displayTextType, + orgName); + noticeNumbers = numbers; + } + + /** + * Creates a new NoticeReference instance. + *

Useful for reconstructing a NoticeReference + * instance from its encodable/encoded form. + * + * @param as an ASN1Sequence value obtained from either + * calling @{link toASN1Object()} for a NoticeReference + * instance or from parsing it from a DER-encoded stream. + */ + public NoticeReference( + ASN1Sequence as) + { + if (as.size() != 2) + { + throw new IllegalArgumentException("Bad sequence size: " + + as.size()); + } + + organization = DisplayText.getInstance(as.getObjectAt(0)); + noticeNumbers = ASN1Sequence.getInstance(as.getObjectAt(1)); + } + + public static NoticeReference getInstance( + Object as) + { + if (as instanceof NoticeReference) + { + return (NoticeReference)as; + } + else if (as instanceof ASN1Sequence) + { + return new NoticeReference((ASN1Sequence)as); + } + + throw new IllegalArgumentException("unknown object in getInstance."); + } + + public DisplayText getOrganization() + { + return organization; + } + + public ASN1Sequence getNoticeNumbers() + { + return noticeNumbers; + } + + /** + * Describe toASN1Object method here. + * + * @return a DERObject value + */ + public DERObject toASN1Object() + { + ASN1EncodableVector av = new ASN1EncodableVector(); + av.add (organization); + av.add (noticeNumbers); + return new DERSequence (av); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/ObjectDigestInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/ObjectDigestInfo.java new file mode 100644 index 000000000..247bdcbe8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/ObjectDigestInfo.java @@ -0,0 +1,192 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DEREnumerated; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * ObjectDigestInfo ASN.1 structure used in v2 attribute certificates. + * + *

+ *  
+ *    ObjectDigestInfo ::= SEQUENCE {
+ *         digestedObjectType  ENUMERATED {
+ *                 publicKey            (0),
+ *                 publicKeyCert        (1),
+ *                 otherObjectTypes     (2) },
+ *                         -- otherObjectTypes MUST NOT
+ *                         -- be used in this profile
+ *         otherObjectTypeID   OBJECT IDENTIFIER OPTIONAL,
+ *         digestAlgorithm     AlgorithmIdentifier,
+ *         objectDigest        BIT STRING
+ *    }
+ *   
+ * 
+ * + */ +public class ObjectDigestInfo + extends ASN1Encodable +{ + /** + * The public key is hashed. + */ + public final static int publicKey = 0; + + /** + * The public key certificate is hashed. + */ + public final static int publicKeyCert = 1; + + /** + * An other object is hashed. + */ + public final static int otherObjectDigest = 2; + + DEREnumerated digestedObjectType; + + DERObjectIdentifier otherObjectTypeID; + + AlgorithmIdentifier digestAlgorithm; + + DERBitString objectDigest; + + public static ObjectDigestInfo getInstance( + Object obj) + { + if (obj == null || obj instanceof ObjectDigestInfo) + { + return (ObjectDigestInfo)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new ObjectDigestInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + + obj.getClass().getName()); + } + + public static ObjectDigestInfo getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + /** + * Constructor from given details. + *

+ * If digestedObjectType is not {@link #publicKeyCert} or + * {@link #publicKey} otherObjectTypeID must be given, + * otherwise it is ignored. + * + * @param digestedObjectType The digest object type. + * @param otherObjectTypeID The object type ID for + * otherObjectDigest. + * @param digestAlgorithm The algorithm identifier for the hash. + * @param objectDigest The hash value. + */ + public ObjectDigestInfo( + int digestedObjectType, + String otherObjectTypeID, + AlgorithmIdentifier digestAlgorithm, + byte[] objectDigest) + { + this.digestedObjectType = new DEREnumerated(digestedObjectType); + if (digestedObjectType == otherObjectDigest) + { + this.otherObjectTypeID = new DERObjectIdentifier(otherObjectTypeID); + } + + this.digestAlgorithm = digestAlgorithm; + + this.objectDigest = new DERBitString(objectDigest); + } + + private ObjectDigestInfo( + ASN1Sequence seq) + { + if (seq.size() > 4 || seq.size() < 3) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + digestedObjectType = DEREnumerated.getInstance(seq.getObjectAt(0)); + + int offset = 0; + + if (seq.size() == 4) + { + otherObjectTypeID = DERObjectIdentifier.getInstance(seq.getObjectAt(1)); + offset++; + } + + digestAlgorithm = AlgorithmIdentifier.getInstance(seq.getObjectAt(1 + offset)); + + objectDigest = DERBitString.getInstance(seq.getObjectAt(2 + offset)); + } + + public DEREnumerated getDigestedObjectType() + { + return digestedObjectType; + } + + public DERObjectIdentifier getOtherObjectTypeID() + { + return otherObjectTypeID; + } + + public AlgorithmIdentifier getDigestAlgorithm() + { + return digestAlgorithm; + } + + public DERBitString getObjectDigest() + { + return objectDigest; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + * + *

+     *  
+     *    ObjectDigestInfo ::= SEQUENCE {
+     *         digestedObjectType  ENUMERATED {
+     *                 publicKey            (0),
+     *                 publicKeyCert        (1),
+     *                 otherObjectTypes     (2) },
+     *                         -- otherObjectTypes MUST NOT
+     *                         -- be used in this profile
+     *         otherObjectTypeID   OBJECT IDENTIFIER OPTIONAL,
+     *         digestAlgorithm     AlgorithmIdentifier,
+     *         objectDigest        BIT STRING
+     *    }
+     *   
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(digestedObjectType); + + if (otherObjectTypeID != null) + { + v.add(otherObjectTypeID); + } + + v.add(digestAlgorithm); + v.add(objectDigest); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyInformation.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyInformation.java new file mode 100644 index 000000000..b02fe97d1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyInformation.java @@ -0,0 +1,87 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class PolicyInformation + extends ASN1Encodable +{ + private DERObjectIdentifier policyIdentifier; + private ASN1Sequence policyQualifiers; + + public PolicyInformation( + ASN1Sequence seq) + { + if (seq.size() < 1 || seq.size() > 2) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + policyIdentifier = DERObjectIdentifier.getInstance(seq.getObjectAt(0)); + + if (seq.size() > 1) + { + policyQualifiers = ASN1Sequence.getInstance(seq.getObjectAt(1)); + } + } + + public PolicyInformation( + DERObjectIdentifier policyIdentifier) + { + this.policyIdentifier = policyIdentifier; + } + + public PolicyInformation( + DERObjectIdentifier policyIdentifier, + ASN1Sequence policyQualifiers) + { + this.policyIdentifier = policyIdentifier; + this.policyQualifiers = policyQualifiers; + } + + public static PolicyInformation getInstance( + Object obj) + { + if (obj == null || obj instanceof PolicyInformation) + { + return (PolicyInformation)obj; + } + + return new PolicyInformation(ASN1Sequence.getInstance(obj)); + } + + public DERObjectIdentifier getPolicyIdentifier() + { + return policyIdentifier; + } + + public ASN1Sequence getPolicyQualifiers() + { + return policyQualifiers; + } + + /* + * PolicyInformation ::= SEQUENCE { + * policyIdentifier CertPolicyId, + * policyQualifiers SEQUENCE SIZE (1..MAX) OF + * PolicyQualifierInfo OPTIONAL } + */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(policyIdentifier); + + if (policyQualifiers != null) + { + v.add(policyQualifiers); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyMappings.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyMappings.java new file mode 100644 index 000000000..9f7383a20 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyMappings.java @@ -0,0 +1,68 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import java.util.Hashtable; +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * PolicyMappings V3 extension, described in RFC3280. + *
+ *    PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE {
+ *      issuerDomainPolicy      CertPolicyId,
+ *      subjectDomainPolicy     CertPolicyId }
+ * 
+ * + * @see
RFC 3280, section 4.2.1.6 + */ +public class PolicyMappings + extends ASN1Encodable +{ + ASN1Sequence seq = null; + + /** + * Creates a new PolicyMappings instance. + * + * @param seq an ASN1Sequence constructed as specified + * in RFC 3280 + */ + public PolicyMappings (ASN1Sequence seq) + { + this.seq = seq; + } + + /** + * Creates a new PolicyMappings instance. + * + * @param mappings a HashMap value that maps + * String oids + * to other String oids. + */ + public PolicyMappings (Hashtable mappings) + { + ASN1EncodableVector dev = new ASN1EncodableVector(); + Enumeration it = mappings.keys(); + + while (it.hasMoreElements()) + { + String idp = (String) it.nextElement(); + String sdp = (String) mappings.get(idp); + ASN1EncodableVector dv = new ASN1EncodableVector(); + dv.add(new DERObjectIdentifier(idp)); + dv.add(new DERObjectIdentifier(sdp)); + dev.add(new DERSequence(dv)); + } + + seq = new DERSequence(dev); + } + + public DERObject toASN1Object() + { + return seq; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyQualifierId.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyQualifierId.java new file mode 100644 index 000000000..03243f2b3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyQualifierId.java @@ -0,0 +1,31 @@ + +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +/** + * PolicyQualifierId, used in the CertificatePolicies + * X509V3 extension. + * + *
+ *    id-qt          OBJECT IDENTIFIER ::=  { id-pkix 2 }
+ *    id-qt-cps      OBJECT IDENTIFIER ::=  { id-qt 1 }
+ *    id-qt-unotice  OBJECT IDENTIFIER ::=  { id-qt 2 }
+ *  PolicyQualifierId ::=
+ *       OBJECT IDENTIFIER (id-qt-cps | id-qt-unotice)
+ * 
+ */ +public class PolicyQualifierId extends DERObjectIdentifier +{ + private static final String id_qt = "1.3.6.1.5.5.7.2"; + + private PolicyQualifierId(String id) + { + super(id); + } + + public static final PolicyQualifierId id_qt_cps = + new PolicyQualifierId(id_qt + ".1"); + public static final PolicyQualifierId id_qt_unotice = + new PolicyQualifierId(id_qt + ".2"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyQualifierInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyQualifierInfo.java new file mode 100644 index 000000000..6338f0012 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/PolicyQualifierInfo.java @@ -0,0 +1,114 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERIA5String; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * Policy qualifiers, used in the X509V3 CertificatePolicies + * extension. + * + *
+ *   PolicyQualifierInfo ::= SEQUENCE {
+ *       policyQualifierId  PolicyQualifierId,
+ *       qualifier          ANY DEFINED BY policyQualifierId }
+ * 
+ */ +public class PolicyQualifierInfo + extends ASN1Encodable +{ + private DERObjectIdentifier policyQualifierId; + private DEREncodable qualifier; + + /** + * Creates a new PolicyQualifierInfo instance. + * + * @param policyQualifierId a PolicyQualifierId value + * @param qualifier the qualifier, defined by the above field. + */ + public PolicyQualifierInfo( + DERObjectIdentifier policyQualifierId, + DEREncodable qualifier) + { + this.policyQualifierId = policyQualifierId; + this.qualifier = qualifier; + } + + /** + * Creates a new PolicyQualifierInfo containing a + * cPSuri qualifier. + * + * @param cps the CPS (certification practice statement) uri as a + * String. + */ + public PolicyQualifierInfo( + String cps) + { + policyQualifierId = PolicyQualifierId.id_qt_cps; + qualifier = new DERIA5String (cps); + } + + /** + * Creates a new PolicyQualifierInfo instance. + * + * @param as PolicyQualifierInfo X509 structure + * encoded as an ASN1Sequence. + */ + public PolicyQualifierInfo( + ASN1Sequence as) + { + if (as.size() != 2) + { + throw new IllegalArgumentException("Bad sequence size: " + + as.size()); + } + + policyQualifierId = DERObjectIdentifier.getInstance(as.getObjectAt(0)); + qualifier = as.getObjectAt(1); + } + + public static PolicyQualifierInfo getInstance( + Object as) + { + if (as instanceof PolicyQualifierInfo) + { + return (PolicyQualifierInfo)as; + } + else if (as instanceof ASN1Sequence) + { + return new PolicyQualifierInfo((ASN1Sequence)as); + } + + throw new IllegalArgumentException("unknown object in getInstance."); + } + + + public DERObjectIdentifier getPolicyQualifierId() + { + return policyQualifierId; + } + + public DEREncodable getQualifier() + { + return qualifier; + } + + /** + * Returns a DER-encodable representation of this instance. + * + * @return a DERObject value + */ + public DERObject toASN1Object() + { + ASN1EncodableVector dev = new ASN1EncodableVector(); + dev.add(policyQualifierId); + dev.add(qualifier); + + return new DERSequence(dev); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/PrivateKeyUsagePeriod.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/PrivateKeyUsagePeriod.java new file mode 100644 index 000000000..5eed1e2ce --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/PrivateKeyUsagePeriod.java @@ -0,0 +1,89 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +import java.util.Enumeration; + +/** + *
+ *    PrivateKeyUsagePeriod ::= SEQUENCE {
+ *      notBefore       [0]     GeneralizedTime OPTIONAL,
+ *      notAfter        [1]     GeneralizedTime OPTIONAL }
+ * 
+ */ +public class PrivateKeyUsagePeriod + extends ASN1Encodable +{ + public static PrivateKeyUsagePeriod getInstance(Object obj) + { + if (obj instanceof PrivateKeyUsagePeriod) + { + return (PrivateKeyUsagePeriod)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new PrivateKeyUsagePeriod((ASN1Sequence)obj); + } + + if (obj instanceof X509Extension) + { + return getInstance(X509Extension.convertValueToObject((X509Extension)obj)); + } + + throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName()); + } + + private DERGeneralizedTime _notBefore, _notAfter; + + private PrivateKeyUsagePeriod(ASN1Sequence seq) + { + Enumeration en = seq.getObjects(); + while (en.hasMoreElements()) + { + ASN1TaggedObject tObj = (ASN1TaggedObject)en.nextElement(); + + if (tObj.getTagNo() == 0) + { + _notBefore = DERGeneralizedTime.getInstance(tObj, false); + } + else if (tObj.getTagNo() == 1) + { + _notAfter = DERGeneralizedTime.getInstance(tObj, false); + } + } + } + + public DERGeneralizedTime getNotBefore() + { + return _notBefore; + } + + public DERGeneralizedTime getNotAfter() + { + return _notAfter; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (_notBefore != null) + { + v.add(new DERTaggedObject(false, 0, _notBefore)); + } + if (_notAfter != null) + { + v.add(new DERTaggedObject(false, 1, _notAfter)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/RSAPublicKeyStructure.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/RSAPublicKeyStructure.java new file mode 100644 index 000000000..a39bba890 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/RSAPublicKeyStructure.java @@ -0,0 +1,95 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import java.math.BigInteger; +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +public class RSAPublicKeyStructure + extends ASN1Encodable +{ + private BigInteger modulus; + private BigInteger publicExponent; + + public static RSAPublicKeyStructure getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static RSAPublicKeyStructure getInstance( + Object obj) + { + if(obj == null || obj instanceof RSAPublicKeyStructure) + { + return (RSAPublicKeyStructure)obj; + } + + if(obj instanceof ASN1Sequence) + { + return new RSAPublicKeyStructure((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("Invalid RSAPublicKeyStructure: " + obj.getClass().getName()); + } + + public RSAPublicKeyStructure( + BigInteger modulus, + BigInteger publicExponent) + { + this.modulus = modulus; + this.publicExponent = publicExponent; + } + + public RSAPublicKeyStructure( + ASN1Sequence seq) + { + if (seq.size() != 2) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + Enumeration e = seq.getObjects(); + + modulus = DERInteger.getInstance(e.nextElement()).getPositiveValue(); + publicExponent = DERInteger.getInstance(e.nextElement()).getPositiveValue(); + } + + public BigInteger getModulus() + { + return modulus; + } + + public BigInteger getPublicExponent() + { + return publicExponent; + } + + /** + * This outputs the key in PKCS1v2 format. + *
+     *      RSAPublicKey ::= SEQUENCE {
+     *                          modulus INTEGER, -- n
+     *                          publicExponent INTEGER, -- e
+     *                      }
+     * 
+ *

+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new DERInteger(getModulus())); + v.add(new DERInteger(getPublicExponent())); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/ReasonFlags.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/ReasonFlags.java new file mode 100644 index 000000000..8f8c1f339 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/ReasonFlags.java @@ -0,0 +1,85 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.DERBitString; + +/** + * The ReasonFlags object. + *

+ * ReasonFlags ::= BIT STRING {
+ *      unused                  (0),
+ *      keyCompromise           (1),
+ *      cACompromise            (2),
+ *      affiliationChanged      (3),
+ *      superseded              (4),
+ *      cessationOfOperation    (5),
+ *      certificateHold         (6),
+ *      privilegeWithdrawn      (7),
+ *      aACompromise            (8) }
+ * 
+ */ +public class ReasonFlags + extends DERBitString +{ + /** + * @deprecated use lower case version + */ + public static final int UNUSED = (1 << 7); + /** + * @deprecated use lower case version + */ + public static final int KEY_COMPROMISE = (1 << 6); + /** + * @deprecated use lower case version + */ + public static final int CA_COMPROMISE = (1 << 5); + /** + * @deprecated use lower case version + */ + public static final int AFFILIATION_CHANGED = (1 << 4); + /** + * @deprecated use lower case version + */ + public static final int SUPERSEDED = (1 << 3); + /** + * @deprecated use lower case version + */ + public static final int CESSATION_OF_OPERATION = (1 << 2); + /** + * @deprecated use lower case version + */ + public static final int CERTIFICATE_HOLD = (1 << 1); + /** + * @deprecated use lower case version + */ + public static final int PRIVILEGE_WITHDRAWN = (1 << 0); + /** + * @deprecated use lower case version + */ + public static final int AA_COMPROMISE = (1 << 15); + + public static final int unused = (1 << 7); + public static final int keyCompromise = (1 << 6); + public static final int cACompromise = (1 << 5); + public static final int affiliationChanged = (1 << 4); + public static final int superseded = (1 << 3); + public static final int cessationOfOperation = (1 << 2); + public static final int certificateHold = (1 << 1); + public static final int privilegeWithdrawn = (1 << 0); + public static final int aACompromise = (1 << 15); + + /** + * @param reasons - the bitwise OR of the Key Reason flags giving the + * allowed uses for the key. + */ + public ReasonFlags( + int reasons) + { + super(getBytes(reasons), getPadBits(reasons)); + } + + public ReasonFlags( + DERBitString reasons) + { + super(reasons.getBytes(), reasons.getPadBits()); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/RoleSyntax.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/RoleSyntax.java new file mode 100644 index 000000000..b6afe3019 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/RoleSyntax.java @@ -0,0 +1,236 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERString; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +/** + * Implementation of the RoleSyntax object as specified by the RFC3281. + * + *
+ * RoleSyntax ::= SEQUENCE {
+ *                 roleAuthority  [0] GeneralNames OPTIONAL,
+ *                 roleName       [1] GeneralName
+ *           } 
+ * 
+ */ +public class RoleSyntax + extends ASN1Encodable +{ + private GeneralNames roleAuthority; + private GeneralName roleName; + + /** + * RoleSyntax factory method. + * @param obj the object used to construct an instance of + * RoleSyntax. It must be an instance of RoleSyntax + * or ASN1Sequence. + * @return the instance of RoleSyntax built from the + * supplied object. + * @throws java.lang.IllegalArgumentException if the object passed + * to the factory is not an instance of RoleSyntax or + * ASN1Sequence. + */ + public static RoleSyntax getInstance( + Object obj) + { + + if(obj == null || obj instanceof RoleSyntax) + { + return (RoleSyntax)obj; + } + else if(obj instanceof ASN1Sequence) + { + return new RoleSyntax((ASN1Sequence)obj); + } + throw new IllegalArgumentException("Unknown object in RoleSyntax factory."); + } + + /** + * Constructor. + * @param roleAuthority the role authority of this RoleSyntax. + * @param roleName the role name of this RoleSyntax. + */ + public RoleSyntax( + GeneralNames roleAuthority, + GeneralName roleName) + { + if(roleName == null || + roleName.getTagNo() != GeneralName.uniformResourceIdentifier || + ((DERString)roleName.getName()).getString().equals("")) + { + throw new IllegalArgumentException("the role name MUST be non empty and MUST " + + "use the URI option of GeneralName"); + } + this.roleAuthority = roleAuthority; + this.roleName = roleName; + } + + /** + * Constructor. Invoking this constructor is the same as invoking + * new RoleSyntax(null, roleName). + * @param roleName the role name of this RoleSyntax. + */ + public RoleSyntax( + GeneralName roleName) + { + this(null, roleName); + } + + /** + * Utility constructor. Takes a String argument representing + * the role name, builds a GeneralName to hold the role name + * and calls the constructor that takes a GeneralName. + * @param roleName + */ + public RoleSyntax( + String roleName) + { + this(new GeneralName(GeneralName.uniformResourceIdentifier, + (roleName == null)? "": roleName)); + } + + /** + * Constructor that builds an instance of RoleSyntax by + * extracting the encoded elements from the ASN1Sequence + * object supplied. + * @param seq an instance of ASN1Sequence that holds + * the encoded elements used to build this RoleSyntax. + */ + public RoleSyntax( + ASN1Sequence seq) + { + if (seq.size() < 1 || seq.size() > 2) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + for (int i = 0; i != seq.size(); i++) + { + ASN1TaggedObject taggedObject = ASN1TaggedObject.getInstance(seq.getObjectAt(i)); + switch (taggedObject.getTagNo()) + { + case 0: + roleAuthority = GeneralNames.getInstance(taggedObject, false); + break; + case 1: + roleName = GeneralName.getInstance(taggedObject, true); + break; + default: + throw new IllegalArgumentException("Unknown tag in RoleSyntax"); + } + } + } + + /** + * Gets the role authority of this RoleSyntax. + * @return an instance of GeneralNames holding the + * role authority of this RoleSyntax. + */ + public GeneralNames getRoleAuthority() + { + return this.roleAuthority; + } + + /** + * Gets the role name of this RoleSyntax. + * @return an instance of GeneralName holding the + * role name of this RoleSyntax. + */ + public GeneralName getRoleName() + { + return this.roleName; + } + + /** + * Gets the role name as a java.lang.String object. + * @return the role name of this RoleSyntax represented as a + * java.lang.String object. + */ + public String getRoleNameAsString() + { + DERString str = (DERString)this.roleName.getName(); + + return str.getString(); + } + + /** + * Gets the role authority as a String[] object. + * @return the role authority of this RoleSyntax represented as a + * String[] array. + */ + public String[] getRoleAuthorityAsString() + { + if(roleAuthority == null) + { + return new String[0]; + } + + GeneralName[] names = roleAuthority.getNames(); + String[] namesString = new String[names.length]; + for(int i = 0; i < names.length; i++) + { + DEREncodable value = names[i].getName(); + if(value instanceof DERString) + { + namesString[i] = ((DERString)value).getString(); + } + else + { + namesString[i] = value.toString(); + } + } + return namesString; + } + + /** + * Implementation of the method toASN1Object as + * required by the superclass ASN1Encodable. + * + *
+     * RoleSyntax ::= SEQUENCE {
+     *                 roleAuthority  [0] GeneralNames OPTIONAL,
+     *                 roleName       [1] GeneralName
+     *           } 
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + if(this.roleAuthority != null) + { + v.add(new DERTaggedObject(false, 0, roleAuthority)); + } + v.add(new DERTaggedObject(true, 1, roleName)); + + return new DERSequence(v); + } + + public String toString() + { + StringBuffer buff = new StringBuffer("Name: " + this.getRoleNameAsString() + + " - Auth: "); + if(this.roleAuthority == null || roleAuthority.getNames().length == 0) + { + buff.append("N/A"); + } + else + { + String[] names = this.getRoleAuthorityAsString(); + buff.append('[').append(names[0]); + for(int i = 1; i < names.length; i++) + { + buff.append(", ").append(names[i]); + } + buff.append(']'); + } + return buff.toString(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/SubjectDirectoryAttributes.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/SubjectDirectoryAttributes.java new file mode 100644 index 000000000..c2fc699d4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/SubjectDirectoryAttributes.java @@ -0,0 +1,144 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import java.util.Enumeration; +import java.util.Vector; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * This extension may contain further X.500 attributes of the subject. See also + * RFC 3039. + * + *
+ *     SubjectDirectoryAttributes ::= Attributes
+ *     Attributes ::= SEQUENCE SIZE (1..MAX) OF Attribute
+ *     Attribute ::= SEQUENCE 
+ *     {
+ *       type AttributeType 
+ *       values SET OF AttributeValue 
+ *     }
+ *     
+ *     AttributeType ::= OBJECT IDENTIFIER
+ *     AttributeValue ::= ANY DEFINED BY AttributeType
+ * 
+ * + * @see com.google.bitcoin.bouncycastle.asn1.x509.X509Name for AttributeType ObjectIdentifiers. + */ +public class SubjectDirectoryAttributes + extends ASN1Encodable +{ + private Vector attributes = new Vector(); + + public static SubjectDirectoryAttributes getInstance( + Object obj) + { + if (obj == null || obj instanceof SubjectDirectoryAttributes) + { + return (SubjectDirectoryAttributes)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new SubjectDirectoryAttributes((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * Constructor from ASN1Sequence. + * + * The sequence is of type SubjectDirectoryAttributes: + * + *
+     *      SubjectDirectoryAttributes ::= Attributes
+     *      Attributes ::= SEQUENCE SIZE (1..MAX) OF Attribute
+     *      Attribute ::= SEQUENCE 
+     *      {
+     *        type AttributeType 
+     *        values SET OF AttributeValue 
+     *      }
+     *      
+     *      AttributeType ::= OBJECT IDENTIFIER
+     *      AttributeValue ::= ANY DEFINED BY AttributeType
+     * 
+ * + * @param seq + * The ASN.1 sequence. + */ + public SubjectDirectoryAttributes(ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + while (e.hasMoreElements()) + { + ASN1Sequence s = ASN1Sequence.getInstance(e.nextElement()); + attributes.addElement(new Attribute(s)); + } + } + + /** + * Constructor from a vector of attributes. + * + * The vector consists of attributes of type {@link Attribute Attribute} + * + * @param attributes + * The attributes. + * + */ + public SubjectDirectoryAttributes(Vector attributes) + { + Enumeration e = attributes.elements(); + + while (e.hasMoreElements()) + { + this.attributes.addElement(e.nextElement()); + } + } + + /** + * Produce an object suitable for an ASN1OutputStream. + * + * Returns: + * + *
+     *      SubjectDirectoryAttributes ::= Attributes
+     *      Attributes ::= SEQUENCE SIZE (1..MAX) OF Attribute
+     *      Attribute ::= SEQUENCE 
+     *      {
+     *        type AttributeType 
+     *        values SET OF AttributeValue 
+     *      }
+     *      
+     *      AttributeType ::= OBJECT IDENTIFIER
+     *      AttributeValue ::= ANY DEFINED BY AttributeType
+     * 
+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + ASN1EncodableVector vec = new ASN1EncodableVector(); + Enumeration e = attributes.elements(); + + while (e.hasMoreElements()) + { + + vec.add((Attribute)e.nextElement()); + } + + return new DERSequence(vec); + } + + /** + * @return Returns the attributes. + */ + public Vector getAttributes() + { + return attributes; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/SubjectKeyIdentifier.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/SubjectKeyIdentifier.java new file mode 100644 index 000000000..5de6b98a1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/SubjectKeyIdentifier.java @@ -0,0 +1,137 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.digests.SHA1Digest; + +/** + * The SubjectKeyIdentifier object. + *
+ * SubjectKeyIdentifier::= OCTET STRING
+ * 
+ */ +public class SubjectKeyIdentifier + extends ASN1Encodable +{ + private byte[] keyidentifier; + + public static SubjectKeyIdentifier getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1OctetString.getInstance(obj, explicit)); + } + + public static SubjectKeyIdentifier getInstance( + Object obj) + { + if (obj instanceof SubjectKeyIdentifier) + { + return (SubjectKeyIdentifier)obj; + } + + if (obj instanceof SubjectPublicKeyInfo) + { + return new SubjectKeyIdentifier((SubjectPublicKeyInfo)obj); + } + + if (obj instanceof ASN1OctetString) + { + return new SubjectKeyIdentifier((ASN1OctetString)obj); + } + + if (obj instanceof X509Extension) + { + return getInstance(X509Extension.convertValueToObject((X509Extension)obj)); + } + + throw new IllegalArgumentException("Invalid SubjectKeyIdentifier: " + obj.getClass().getName()); + } + + public SubjectKeyIdentifier( + byte[] keyid) + { + this.keyidentifier=keyid; + } + + public SubjectKeyIdentifier( + ASN1OctetString keyid) + { + this.keyidentifier=keyid.getOctets(); + } + + /** + * Calculates the keyidentifier using a SHA1 hash over the BIT STRING + * from SubjectPublicKeyInfo as defined in RFC3280. + * + * @param spki the subject public key info. + */ + public SubjectKeyIdentifier( + SubjectPublicKeyInfo spki) + { + this.keyidentifier = getDigest(spki); + } + + public byte[] getKeyIdentifier() + { + return keyidentifier; + } + + public DERObject toASN1Object() + { + return new DEROctetString(keyidentifier); + } + + /** + * Return a RFC 3280 type 1 key identifier. As in: + *
+     * (1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the
+     * value of the BIT STRING subjectPublicKey (excluding the tag,
+     * length, and number of unused bits).
+     * 
+ * @param keyInfo the key info object containing the subjectPublicKey field. + * @return the key identifier. + */ + public static SubjectKeyIdentifier createSHA1KeyIdentifier(SubjectPublicKeyInfo keyInfo) + { + return new SubjectKeyIdentifier(keyInfo); + } + + /** + * Return a RFC 3280 type 2 key identifier. As in: + *
+     * (2) The keyIdentifier is composed of a four bit type field with
+     * the value 0100 followed by the least significant 60 bits of the
+     * SHA-1 hash of the value of the BIT STRING subjectPublicKey.
+     * 
+ * @param keyInfo the key info object containing the subjectPublicKey field. + * @return the key identifier. + */ + public static SubjectKeyIdentifier createTruncatedSHA1KeyIdentifier(SubjectPublicKeyInfo keyInfo) + { + byte[] dig = getDigest(keyInfo); + byte[] id = new byte[8]; + + System.arraycopy(dig, dig.length - 8, id, 0, id.length); + + id[0] &= 0x0f; + id[0] |= 0x40; + + return new SubjectKeyIdentifier(id); + } + + private static byte[] getDigest(SubjectPublicKeyInfo spki) + { + Digest digest = new SHA1Digest(); + byte[] resBuf = new byte[digest.getDigestSize()]; + + byte[] bytes = spki.getPublicKeyData().getBytes(); + digest.update(bytes, 0, bytes.length); + digest.doFinal(resBuf, 0); + return resBuf; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java new file mode 100644 index 000000000..c39dab900 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java @@ -0,0 +1,126 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import java.io.IOException; +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1InputStream; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * The object that contains the public key stored in a certficate. + *

+ * The getEncoded() method in the public keys in the JCE produces a DER + * encoded one of these. + */ +public class SubjectPublicKeyInfo + extends ASN1Encodable +{ + private AlgorithmIdentifier algId; + private DERBitString keyData; + + public static SubjectPublicKeyInfo getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static SubjectPublicKeyInfo getInstance( + Object obj) + { + if (obj instanceof SubjectPublicKeyInfo) + { + return (SubjectPublicKeyInfo)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new SubjectPublicKeyInfo((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public SubjectPublicKeyInfo( + AlgorithmIdentifier algId, + DEREncodable publicKey) + { + this.keyData = new DERBitString(publicKey); + this.algId = algId; + } + + public SubjectPublicKeyInfo( + AlgorithmIdentifier algId, + byte[] publicKey) + { + this.keyData = new DERBitString(publicKey); + this.algId = algId; + } + + public SubjectPublicKeyInfo( + ASN1Sequence seq) + { + if (seq.size() != 2) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + Enumeration e = seq.getObjects(); + + this.algId = AlgorithmIdentifier.getInstance(e.nextElement()); + this.keyData = DERBitString.getInstance(e.nextElement()); + } + + public AlgorithmIdentifier getAlgorithmId() + { + return algId; + } + + /** + * for when the public key is an encoded object - if the bitstring + * can't be decoded this routine throws an IOException. + * + * @exception IOException - if the bit string doesn't represent a DER + * encoded object. + */ + public DERObject getPublicKey() + throws IOException + { + ASN1InputStream aIn = new ASN1InputStream(keyData.getBytes()); + + return aIn.readObject(); + } + + /** + * for when the public key is raw bits... + */ + public DERBitString getPublicKeyData() + { + return keyData; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+     * SubjectPublicKeyInfo ::= SEQUENCE {
+     *                          algorithm AlgorithmIdentifier,
+     *                          publicKey BIT STRING }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(algId); + v.add(keyData); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/TBSCertList.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/TBSCertList.java new file mode 100644 index 000000000..ae5c2341a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/TBSCertList.java @@ -0,0 +1,266 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERUTCTime; + +import java.util.Enumeration; + +/** + * PKIX RFC-2459 - TBSCertList object. + *
+ * TBSCertList  ::=  SEQUENCE  {
+ *      version                 Version OPTIONAL,
+ *                                   -- if present, shall be v2
+ *      signature               AlgorithmIdentifier,
+ *      issuer                  Name,
+ *      thisUpdate              Time,
+ *      nextUpdate              Time OPTIONAL,
+ *      revokedCertificates     SEQUENCE OF SEQUENCE  {
+ *           userCertificate         CertificateSerialNumber,
+ *           revocationDate          Time,
+ *           crlEntryExtensions      Extensions OPTIONAL
+ *                                         -- if present, shall be v2
+ *                                }  OPTIONAL,
+ *      crlExtensions           [0]  EXPLICIT Extensions OPTIONAL
+ *                                         -- if present, shall be v2
+ *                                }
+ * 
+ */ +public class TBSCertList + extends ASN1Encodable +{ + public class CRLEntry + extends ASN1Encodable + { + ASN1Sequence seq; + + DERInteger userCertificate; + Time revocationDate; + X509Extensions crlEntryExtensions; + + public CRLEntry( + ASN1Sequence seq) + { + if (seq.size() < 2 || seq.size() > 3) + { + throw new IllegalArgumentException("Bad sequence size: " + seq.size()); + } + + this.seq = seq; + + userCertificate = DERInteger.getInstance(seq.getObjectAt(0)); + revocationDate = Time.getInstance(seq.getObjectAt(1)); + } + + public DERInteger getUserCertificate() + { + return userCertificate; + } + + public Time getRevocationDate() + { + return revocationDate; + } + + public X509Extensions getExtensions() + { + if (crlEntryExtensions == null && seq.size() == 3) + { + crlEntryExtensions = X509Extensions.getInstance(seq.getObjectAt(2)); + } + + return crlEntryExtensions; + } + + public DERObject toASN1Object() + { + return seq; + } + } + + private class RevokedCertificatesEnumeration + implements Enumeration + { + private final Enumeration en; + + RevokedCertificatesEnumeration(Enumeration en) + { + this.en = en; + } + + public boolean hasMoreElements() + { + return en.hasMoreElements(); + } + + public Object nextElement() + { + return new CRLEntry(ASN1Sequence.getInstance(en.nextElement())); + } + } + + private class EmptyEnumeration + implements Enumeration + { + public boolean hasMoreElements() + { + return false; + } + + public Object nextElement() + { + return null; // TODO: check exception handling + } + } + + ASN1Sequence seq; + + DERInteger version; + AlgorithmIdentifier signature; + X509Name issuer; + Time thisUpdate; + Time nextUpdate; + ASN1Sequence revokedCertificates; + X509Extensions crlExtensions; + + public static TBSCertList getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static TBSCertList getInstance( + Object obj) + { + if (obj instanceof TBSCertList) + { + return (TBSCertList)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new TBSCertList((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public TBSCertList( + ASN1Sequence seq) + { + if (seq.size() < 3 || seq.size() > 7) + { + throw new IllegalArgumentException("Bad sequence size: " + seq.size()); + } + + int seqPos = 0; + + this.seq = seq; + + if (seq.getObjectAt(seqPos) instanceof DERInteger) + { + version = DERInteger.getInstance(seq.getObjectAt(seqPos++)); + } + else + { + version = new DERInteger(0); + } + + signature = AlgorithmIdentifier.getInstance(seq.getObjectAt(seqPos++)); + issuer = X509Name.getInstance(seq.getObjectAt(seqPos++)); + thisUpdate = Time.getInstance(seq.getObjectAt(seqPos++)); + + if (seqPos < seq.size() + && (seq.getObjectAt(seqPos) instanceof DERUTCTime + || seq.getObjectAt(seqPos) instanceof DERGeneralizedTime + || seq.getObjectAt(seqPos) instanceof Time)) + { + nextUpdate = Time.getInstance(seq.getObjectAt(seqPos++)); + } + + if (seqPos < seq.size() + && !(seq.getObjectAt(seqPos) instanceof DERTaggedObject)) + { + revokedCertificates = ASN1Sequence.getInstance(seq.getObjectAt(seqPos++)); + } + + if (seqPos < seq.size() + && seq.getObjectAt(seqPos) instanceof DERTaggedObject) + { + crlExtensions = X509Extensions.getInstance(seq.getObjectAt(seqPos)); + } + } + + public int getVersion() + { + return version.getValue().intValue() + 1; + } + + public DERInteger getVersionNumber() + { + return version; + } + + public AlgorithmIdentifier getSignature() + { + return signature; + } + + public X509Name getIssuer() + { + return issuer; + } + + public Time getThisUpdate() + { + return thisUpdate; + } + + public Time getNextUpdate() + { + return nextUpdate; + } + + public CRLEntry[] getRevokedCertificates() + { + if (revokedCertificates == null) + { + return new CRLEntry[0]; + } + + CRLEntry[] entries = new CRLEntry[revokedCertificates.size()]; + + for (int i = 0; i < entries.length; i++) + { + entries[i] = new CRLEntry(ASN1Sequence.getInstance(revokedCertificates.getObjectAt(i))); + } + + return entries; + } + + public Enumeration getRevokedCertificateEnumeration() + { + if (revokedCertificates == null) + { + return new EmptyEnumeration(); + } + + return new RevokedCertificatesEnumeration(revokedCertificates.getObjects()); + } + + public X509Extensions getExtensions() + { + return crlExtensions; + } + + public DERObject toASN1Object() + { + return seq; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/TBSCertificateStructure.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/TBSCertificateStructure.java new file mode 100644 index 000000000..ed4dbac08 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/TBSCertificateStructure.java @@ -0,0 +1,193 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; + +/** + * The TBSCertificate object. + *
+ * TBSCertificate ::= SEQUENCE {
+ *      version          [ 0 ]  Version DEFAULT v1(0),
+ *      serialNumber            CertificateSerialNumber,
+ *      signature               AlgorithmIdentifier,
+ *      issuer                  Name,
+ *      validity                Validity,
+ *      subject                 Name,
+ *      subjectPublicKeyInfo    SubjectPublicKeyInfo,
+ *      issuerUniqueID    [ 1 ] IMPLICIT UniqueIdentifier OPTIONAL,
+ *      subjectUniqueID   [ 2 ] IMPLICIT UniqueIdentifier OPTIONAL,
+ *      extensions        [ 3 ] Extensions OPTIONAL
+ *      }
+ * 
+ *

+ * Note: issuerUniqueID and subjectUniqueID are both deprecated by the IETF. This class + * will parse them, but you really shouldn't be creating new ones. + */ +public class TBSCertificateStructure + extends ASN1Encodable + implements X509ObjectIdentifiers, PKCSObjectIdentifiers +{ + ASN1Sequence seq; + + DERInteger version; + DERInteger serialNumber; + AlgorithmIdentifier signature; + X509Name issuer; + Time startDate, endDate; + X509Name subject; + SubjectPublicKeyInfo subjectPublicKeyInfo; + DERBitString issuerUniqueId; + DERBitString subjectUniqueId; + X509Extensions extensions; + + public static TBSCertificateStructure getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static TBSCertificateStructure getInstance( + Object obj) + { + if (obj instanceof TBSCertificateStructure) + { + return (TBSCertificateStructure)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new TBSCertificateStructure((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public TBSCertificateStructure( + ASN1Sequence seq) + { + int seqStart = 0; + + this.seq = seq; + + // + // some certficates don't include a version number - we assume v1 + // + if (seq.getObjectAt(0) instanceof DERTaggedObject) + { + version = DERInteger.getInstance(seq.getObjectAt(0)); + } + else + { + seqStart = -1; // field 0 is missing! + version = new DERInteger(0); + } + + serialNumber = DERInteger.getInstance(seq.getObjectAt(seqStart + 1)); + + signature = AlgorithmIdentifier.getInstance(seq.getObjectAt(seqStart + 2)); + issuer = X509Name.getInstance(seq.getObjectAt(seqStart + 3)); + + // + // before and after dates + // + ASN1Sequence dates = (ASN1Sequence)seq.getObjectAt(seqStart + 4); + + startDate = Time.getInstance(dates.getObjectAt(0)); + endDate = Time.getInstance(dates.getObjectAt(1)); + + subject = X509Name.getInstance(seq.getObjectAt(seqStart + 5)); + + // + // public key info. + // + subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(seq.getObjectAt(seqStart + 6)); + + for (int extras = seq.size() - (seqStart + 6) - 1; extras > 0; extras--) + { + DERTaggedObject extra = (DERTaggedObject)seq.getObjectAt(seqStart + 6 + extras); + + switch (extra.getTagNo()) + { + case 1: + issuerUniqueId = DERBitString.getInstance(extra, false); + break; + case 2: + subjectUniqueId = DERBitString.getInstance(extra, false); + break; + case 3: + extensions = X509Extensions.getInstance(extra); + } + } + } + + public int getVersion() + { + return version.getValue().intValue() + 1; + } + + public DERInteger getVersionNumber() + { + return version; + } + + public DERInteger getSerialNumber() + { + return serialNumber; + } + + public AlgorithmIdentifier getSignature() + { + return signature; + } + + public X509Name getIssuer() + { + return issuer; + } + + public Time getStartDate() + { + return startDate; + } + + public Time getEndDate() + { + return endDate; + } + + public X509Name getSubject() + { + return subject; + } + + public SubjectPublicKeyInfo getSubjectPublicKeyInfo() + { + return subjectPublicKeyInfo; + } + + public DERBitString getIssuerUniqueId() + { + return issuerUniqueId; + } + + public DERBitString getSubjectUniqueId() + { + return subjectUniqueId; + } + + public X509Extensions getExtensions() + { + return extensions; + } + + public DERObject toASN1Object() + { + return seq; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/Target.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/Target.java new file mode 100644 index 000000000..965e486f3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/Target.java @@ -0,0 +1,138 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +/** + * Target structure used in target information extension for attribute + * certificates from RFC 3281. + * + *

+ *     Target  ::= CHOICE {
+ *       targetName          [0] GeneralName,
+ *       targetGroup         [1] GeneralName,
+ *       targetCert          [2] TargetCert
+ *     }
+ * 
+ * + *

+ * The targetCert field is currently not supported and must not be used + * according to RFC 3281. + */ +public class Target + extends ASN1Encodable + implements ASN1Choice +{ + public static final int targetName = 0; + public static final int targetGroup = 1; + + private GeneralName targName; + private GeneralName targGroup; + + /** + * Creates an instance of a Target from the given object. + *

+ * obj can be a Target or a {@link ASN1TaggedObject} + * + * @param obj The object. + * @return A Target instance. + * @throws IllegalArgumentException if the given object cannot be + * interpreted as Target. + */ + public static Target getInstance(Object obj) + { + if (obj instanceof Target) + { + return (Target) obj; + } + else if (obj instanceof ASN1TaggedObject) + { + return new Target((ASN1TaggedObject)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + + obj.getClass()); + } + + /** + * Constructor from ASN1TaggedObject. + * + * @param tagObj The tagged object. + * @throws IllegalArgumentException if the encoding is wrong. + */ + private Target(ASN1TaggedObject tagObj) + { + switch (tagObj.getTagNo()) + { + case targetName: // GeneralName is already a choice so explicit + targName = GeneralName.getInstance(tagObj, true); + break; + case targetGroup: + targGroup = GeneralName.getInstance(tagObj, true); + break; + default: + throw new IllegalArgumentException("unknown tag: " + tagObj.getTagNo()); + } + } + + /** + * Constructor from given details. + *

+ * Exactly one of the parameters must be not null. + * + * @param type the choice type to apply to the name. + * @param name the general name. + * @throws IllegalArgumentException if type is invalid. + */ + public Target(int type, GeneralName name) + { + this(new DERTaggedObject(type, name)); + } + + /** + * @return Returns the targetGroup. + */ + public GeneralName getTargetGroup() + { + return targGroup; + } + + /** + * @return Returns the targetName. + */ + public GeneralName getTargetName() + { + return targName; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + * + * Returns: + * + *

+     *     Target  ::= CHOICE {
+     *       targetName          [0] GeneralName,
+     *       targetGroup         [1] GeneralName,
+     *       targetCert          [2] TargetCert
+     *     }
+     * 
+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + // GeneralName is a choice already so most be explicitly tagged + if (targName != null) + { + return new DERTaggedObject(true, 0, targName); + } + else + { + return new DERTaggedObject(true, 1, targGroup); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/TargetInformation.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/TargetInformation.java new file mode 100644 index 000000000..3c5868c32 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/TargetInformation.java @@ -0,0 +1,121 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +import java.util.Enumeration; + +/** + * Target information extension for attributes certificates according to RFC + * 3281. + * + *
+ *           SEQUENCE OF Targets
+ * 
+ * + */ +public class TargetInformation + extends ASN1Encodable +{ + private ASN1Sequence targets; + + /** + * Creates an instance of a TargetInformation from the given object. + *

+ * obj can be a TargetInformation or a {@link ASN1Sequence} + * + * @param obj The object. + * @return A TargetInformation instance. + * @throws IllegalArgumentException if the given object cannot be + * interpreted as TargetInformation. + */ + public static TargetInformation getInstance(Object obj) + { + if (obj instanceof TargetInformation) + { + return (TargetInformation) obj; + } + else if (obj instanceof ASN1Sequence) + { + return new TargetInformation((ASN1Sequence) obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + + obj.getClass()); + } + + /** + * Constructor from a ASN1Sequence. + * + * @param seq The ASN1Sequence. + * @throws IllegalArgumentException if the sequence does not contain + * correctly encoded Targets elements. + */ + private TargetInformation(ASN1Sequence seq) + { + targets = seq; + } + + /** + * Returns the targets in this target information extension. + * + * @return Returns the targets. + */ + public Targets[] getTargetsObjects() + { + Targets[] copy = new Targets[targets.size()]; + int count = 0; + for (Enumeration e = targets.getObjects(); e.hasMoreElements();) + { + copy[count++] = Targets.getInstance(e.nextElement()); + } + return copy; + } + + /** + * Constructs a target information from a single targets element. + * According to RFC 3281 only one targets element must be produced. + * + * @param targets A Targets instance. + */ + public TargetInformation(Targets targets) + { + this.targets = new DERSequence(targets); + } + + /** + * According to RFC 3281 only one targets element must be produced. If + * multiple targets are given they must be merged in + * into one targets element. + * + * @param targets An array with {@link Targets}. + */ + public TargetInformation(Target[] targets) + { + this(new Targets(targets)); + } + + /** + * Produce an object suitable for an ASN1OutputStream. + * + * Returns: + * + *

+     *          SEQUENCE OF Targets
+     * 
+ * + *

+ * According to RFC 3281 only one targets element must be produced. If + * multiple targets are given in the constructor they are merged into one + * targets element. If this was produced from a + * {@link com.google.bitcoin.bouncycastle.asn1.ASN1Sequence} the encoding is kept. + * + * @return a DERObject + */ + public DERObject toASN1Object() + { + return targets; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/Targets.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/Targets.java new file mode 100644 index 000000000..a880a430f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/Targets.java @@ -0,0 +1,122 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +import java.util.Enumeration; + +/** + * Targets structure used in target information extension for attribute + * certificates from RFC 3281. + * + *

+ *            Targets ::= SEQUENCE OF Target
+ *           
+ *            Target  ::= CHOICE {
+ *              targetName          [0] GeneralName,
+ *              targetGroup         [1] GeneralName,
+ *              targetCert          [2] TargetCert
+ *            }
+ *           
+ *            TargetCert  ::= SEQUENCE {
+ *              targetCertificate    IssuerSerial,
+ *              targetName           GeneralName OPTIONAL,
+ *              certDigestInfo       ObjectDigestInfo OPTIONAL
+ *            }
+ * 
+ * + * @see com.google.bitcoin.bouncycastle.asn1.x509.Target + * @see com.google.bitcoin.bouncycastle.asn1.x509.TargetInformation + */ +public class Targets + extends ASN1Encodable +{ + private ASN1Sequence targets; + + /** + * Creates an instance of a Targets from the given object. + *

+ * obj can be a Targets or a {@link ASN1Sequence} + * + * @param obj The object. + * @return A Targets instance. + * @throws IllegalArgumentException if the given object cannot be + * interpreted as Target. + */ + public static Targets getInstance(Object obj) + { + if (obj instanceof Targets) + { + return (Targets)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new Targets((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + + obj.getClass()); + } + + /** + * Constructor from ASN1Sequence. + * + * @param targets The ASN.1 SEQUENCE. + * @throws IllegalArgumentException if the contents of the sequence are + * invalid. + */ + private Targets(ASN1Sequence targets) + { + this.targets = targets; + } + + /** + * Constructor from given targets. + *

+ * The vector is copied. + * + * @param targets A Vector of {@link Target}s. + * @see Target + * @throws IllegalArgumentException if the vector contains not only Targets. + */ + public Targets(Target[] targets) + { + this.targets = new DERSequence(targets); + } + + /** + * Returns the targets in a Vector. + *

+ * The vector is cloned before it is returned. + * + * @return Returns the targets. + */ + public Target[] getTargets() + { + Target[] targs = new Target[targets.size()]; + int count = 0; + for (Enumeration e = targets.getObjects(); e.hasMoreElements();) + { + targs[count++] = Target.getInstance(e.nextElement()); + } + return targs; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + * + * Returns: + * + *

+     *            Targets ::= SEQUENCE OF Target
+     * 
+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + return targets; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/Time.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/Time.java new file mode 100644 index 000000000..1416566fc --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/Time.java @@ -0,0 +1,133 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERUTCTime; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.SimpleTimeZone; + +public class Time + extends ASN1Encodable + implements ASN1Choice +{ + DERObject time; + + public static Time getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); // must be explicitly tagged + } + + public Time( + DERObject time) + { + if (!(time instanceof DERUTCTime) + && !(time instanceof DERGeneralizedTime)) + { + throw new IllegalArgumentException("unknown object passed to Time"); + } + + this.time = time; + } + + /** + * creates a time object from a given date - if the date is between 1950 + * and 2049 a UTCTime object is generated, otherwise a GeneralizedTime + * is used. + */ + public Time( + Date date) + { + SimpleTimeZone tz = new SimpleTimeZone(0, "Z"); + SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmss"); + + dateF.setTimeZone(tz); + + String d = dateF.format(date) + "Z"; + int year = Integer.parseInt(d.substring(0, 4)); + + if (year < 1950 || year > 2049) + { + time = new DERGeneralizedTime(d); + } + else + { + time = new DERUTCTime(d.substring(2)); + } + } + + public static Time getInstance( + Object obj) + { + if (obj instanceof Time) + { + return (Time)obj; + } + else if (obj instanceof DERUTCTime) + { + return new Time((DERUTCTime)obj); + } + else if (obj instanceof DERGeneralizedTime) + { + return new Time((DERGeneralizedTime)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public String getTime() + { + if (time instanceof DERUTCTime) + { + return ((DERUTCTime)time).getAdjustedTime(); + } + else + { + return ((DERGeneralizedTime)time).getTime(); + } + } + + public Date getDate() + { + try + { + if (time instanceof DERUTCTime) + { + return ((DERUTCTime)time).getAdjustedDate(); + } + else + { + return ((DERGeneralizedTime)time).getDate(); + } + } + catch (ParseException e) + { // this should never happen + throw new IllegalStateException("invalid date string: " + e.getMessage()); + } + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * Time ::= CHOICE {
+     *             utcTime        UTCTime,
+     *             generalTime    GeneralizedTime }
+     * 
+ */ + public DERObject toASN1Object() + { + return time; + } + + public String toString() + { + return getTime(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/UserNotice.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/UserNotice.java new file mode 100644 index 000000000..463fcd8d1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/UserNotice.java @@ -0,0 +1,117 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * UserNotice class, used in + * CertificatePolicies X509 extensions (in policy + * qualifiers). + *
+ * UserNotice ::= SEQUENCE {
+ *      noticeRef        NoticeReference OPTIONAL,
+ *      explicitText     DisplayText OPTIONAL}
+ *
+ * 
+ * + * @see PolicyQualifierId + * @see PolicyInformation + */ +public class UserNotice + extends ASN1Encodable +{ + private NoticeReference noticeRef; + private DisplayText explicitText; + + /** + * Creates a new UserNotice instance. + * + * @param noticeRef a NoticeReference value + * @param explicitText a DisplayText value + */ + public UserNotice( + NoticeReference noticeRef, + DisplayText explicitText) + { + this.noticeRef = noticeRef; + this.explicitText = explicitText; + } + + /** + * Creates a new UserNotice instance. + * + * @param noticeRef a NoticeReference value + * @param str the explicitText field as a String. + */ + public UserNotice( + NoticeReference noticeRef, + String str) + { + this.noticeRef = noticeRef; + this.explicitText = new DisplayText(str); + } + + /** + * Creates a new UserNotice instance. + *

Useful from reconstructing a UserNotice instance + * from its encodable/encoded form. + * + * @param as an ASN1Sequence value obtained from either + * calling @{link toASN1Object()} for a UserNotice + * instance or from parsing it from a DER-encoded stream. + */ + public UserNotice( + ASN1Sequence as) + { + if (as.size() == 2) + { + noticeRef = NoticeReference.getInstance(as.getObjectAt(0)); + explicitText = DisplayText.getInstance(as.getObjectAt(1)); + } + else if (as.size() == 1) + { + if (as.getObjectAt(0).getDERObject() instanceof ASN1Sequence) + { + noticeRef = NoticeReference.getInstance(as.getObjectAt(0)); + } + else + { + explicitText = DisplayText.getInstance(as.getObjectAt(0)); + } + } + else + { + throw new IllegalArgumentException("Bad sequence size: " + as.size()); + } + } + + public NoticeReference getNoticeRef() + { + return noticeRef; + } + + public DisplayText getExplicitText() + { + return explicitText; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector av = new ASN1EncodableVector(); + + if (noticeRef != null) + { + av.add(noticeRef); + } + + if (explicitText != null) + { + av.add(explicitText); + } + + return new DERSequence(av); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/V1TBSCertificateGenerator.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/V1TBSCertificateGenerator.java new file mode 100644 index 000000000..620e3afe5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/V1TBSCertificateGenerator.java @@ -0,0 +1,125 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERUTCTime; + +/** + * Generator for Version 1 TBSCertificateStructures. + *

+ * TBSCertificate ::= SEQUENCE {
+ *      version          [ 0 ]  Version DEFAULT v1(0),
+ *      serialNumber            CertificateSerialNumber,
+ *      signature               AlgorithmIdentifier,
+ *      issuer                  Name,
+ *      validity                Validity,
+ *      subject                 Name,
+ *      subjectPublicKeyInfo    SubjectPublicKeyInfo,
+ *      }
+ * 
+ * + */ +public class V1TBSCertificateGenerator +{ + DERTaggedObject version = new DERTaggedObject(0, new DERInteger(0)); + + DERInteger serialNumber; + AlgorithmIdentifier signature; + X509Name issuer; + Time startDate, endDate; + X509Name subject; + SubjectPublicKeyInfo subjectPublicKeyInfo; + + public V1TBSCertificateGenerator() + { + } + + public void setSerialNumber( + DERInteger serialNumber) + { + this.serialNumber = serialNumber; + } + + public void setSignature( + AlgorithmIdentifier signature) + { + this.signature = signature; + } + + public void setIssuer( + X509Name issuer) + { + this.issuer = issuer; + } + + public void setStartDate( + Time startDate) + { + this.startDate = startDate; + } + + public void setStartDate( + DERUTCTime startDate) + { + this.startDate = new Time(startDate); + } + + public void setEndDate( + Time endDate) + { + this.endDate = endDate; + } + + public void setEndDate( + DERUTCTime endDate) + { + this.endDate = new Time(endDate); + } + + public void setSubject( + X509Name subject) + { + this.subject = subject; + } + + public void setSubjectPublicKeyInfo( + SubjectPublicKeyInfo pubKeyInfo) + { + this.subjectPublicKeyInfo = pubKeyInfo; + } + + public TBSCertificateStructure generateTBSCertificate() + { + if ((serialNumber == null) || (signature == null) + || (issuer == null) || (startDate == null) || (endDate == null) + || (subject == null) || (subjectPublicKeyInfo == null)) + { + throw new IllegalStateException("not all mandatory fields set in V1 TBScertificate generator"); + } + + ASN1EncodableVector seq = new ASN1EncodableVector(); + + // seq.add(version); - not required as default value. + seq.add(serialNumber); + seq.add(signature); + seq.add(issuer); + + // + // before and after dates + // + ASN1EncodableVector validity = new ASN1EncodableVector(); + + validity.add(startDate); + validity.add(endDate); + + seq.add(new DERSequence(validity)); + + seq.add(subject); + + seq.add(subjectPublicKeyInfo); + + return new TBSCertificateStructure(new DERSequence(seq)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/V2AttributeCertificateInfoGenerator.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/V2AttributeCertificateInfoGenerator.java new file mode 100644 index 000000000..2a3cb6d4d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/V2AttributeCertificateInfoGenerator.java @@ -0,0 +1,148 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERSet; + +/** + * Generator for Version 2 AttributeCertificateInfo + *
+ * AttributeCertificateInfo ::= SEQUENCE {
+ *       version              AttCertVersion -- version is v2,
+ *       holder               Holder,
+ *       issuer               AttCertIssuer,
+ *       signature            AlgorithmIdentifier,
+ *       serialNumber         CertificateSerialNumber,
+ *       attrCertValidityPeriod   AttCertValidityPeriod,
+ *       attributes           SEQUENCE OF Attribute,
+ *       issuerUniqueID       UniqueIdentifier OPTIONAL,
+ *       extensions           Extensions OPTIONAL
+ * }
+ * 
+ * + */ +public class V2AttributeCertificateInfoGenerator +{ + private DERInteger version; + private Holder holder; + private AttCertIssuer issuer; + private AlgorithmIdentifier signature; + private DERInteger serialNumber; + private ASN1EncodableVector attributes; + private DERBitString issuerUniqueID; + private X509Extensions extensions; + + // Note: validity period start/end dates stored directly + //private AttCertValidityPeriod attrCertValidityPeriod; + private DERGeneralizedTime startDate, endDate; + + public V2AttributeCertificateInfoGenerator() + { + this.version = new DERInteger(1); + attributes = new ASN1EncodableVector(); + } + + public void setHolder(Holder holder) + { + this.holder = holder; + } + + public void addAttribute(String oid, ASN1Encodable value) + { + attributes.add(new Attribute(new DERObjectIdentifier(oid), new DERSet(value))); + } + + /** + * @param attribute + */ + public void addAttribute(Attribute attribute) + { + attributes.add(attribute); + } + + public void setSerialNumber( + DERInteger serialNumber) + { + this.serialNumber = serialNumber; + } + + public void setSignature( + AlgorithmIdentifier signature) + { + this.signature = signature; + } + + public void setIssuer( + AttCertIssuer issuer) + { + this.issuer = issuer; + } + + public void setStartDate( + DERGeneralizedTime startDate) + { + this.startDate = startDate; + } + + public void setEndDate( + DERGeneralizedTime endDate) + { + this.endDate = endDate; + } + + public void setIssuerUniqueID( + DERBitString issuerUniqueID) + { + this.issuerUniqueID = issuerUniqueID; + } + + public void setExtensions( + X509Extensions extensions) + { + this.extensions = extensions; + } + + public AttributeCertificateInfo generateAttributeCertificateInfo() + { + if ((serialNumber == null) || (signature == null) + || (issuer == null) || (startDate == null) || (endDate == null) + || (holder == null) || (attributes == null)) + { + throw new IllegalStateException("not all mandatory fields set in V2 AttributeCertificateInfo generator"); + } + + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(holder); + v.add(issuer); + v.add(signature); + v.add(serialNumber); + + // + // before and after dates => AttCertValidityPeriod + // + AttCertValidityPeriod validity = new AttCertValidityPeriod(startDate, endDate); + v.add(validity); + + // Attributes + v.add(new DERSequence(attributes)); + + if (issuerUniqueID != null) + { + v.add(issuerUniqueID); + } + + if (extensions != null) + { + v.add(extensions); + } + + return new AttributeCertificateInfo(new DERSequence(v)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/V2Form.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/V2Form.java new file mode 100644 index 000000000..d9ac8b088 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/V2Form.java @@ -0,0 +1,130 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +public class V2Form + extends ASN1Encodable +{ + GeneralNames issuerName; + IssuerSerial baseCertificateID; + ObjectDigestInfo objectDigestInfo; + + public static V2Form getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static V2Form getInstance( + Object obj) + { + if (obj == null || obj instanceof V2Form) + { + return (V2Form)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new V2Form((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + public V2Form( + GeneralNames issuerName) + { + this.issuerName = issuerName; + } + + public V2Form( + ASN1Sequence seq) + { + if (seq.size() > 3) + { + throw new IllegalArgumentException("Bad sequence size: " + seq.size()); + } + + int index = 0; + + if (!(seq.getObjectAt(0) instanceof ASN1TaggedObject)) + { + index++; + this.issuerName = GeneralNames.getInstance(seq.getObjectAt(0)); + } + + for (int i = index; i != seq.size(); i++) + { + ASN1TaggedObject o = ASN1TaggedObject.getInstance(seq.getObjectAt(i)); + if (o.getTagNo() == 0) + { + baseCertificateID = IssuerSerial.getInstance(o, false); + } + else if (o.getTagNo() == 1) + { + objectDigestInfo = ObjectDigestInfo.getInstance(o, false); + } + else + { + throw new IllegalArgumentException("Bad tag number: " + + o.getTagNo()); + } + } + } + + public GeneralNames getIssuerName() + { + return issuerName; + } + + public IssuerSerial getBaseCertificateID() + { + return baseCertificateID; + } + + public ObjectDigestInfo getObjectDigestInfo() + { + return objectDigestInfo; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  V2Form ::= SEQUENCE {
+     *       issuerName            GeneralNames  OPTIONAL,
+     *       baseCertificateID     [0] IssuerSerial  OPTIONAL,
+     *       objectDigestInfo      [1] ObjectDigestInfo  OPTIONAL
+     *         -- issuerName MUST be present in this profile
+     *         -- baseCertificateID and objectDigestInfo MUST NOT
+     *         -- be present in this profile
+     *  }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (issuerName != null) + { + v.add(issuerName); + } + + if (baseCertificateID != null) + { + v.add(new DERTaggedObject(false, 0, baseCertificateID)); + } + + if (objectDigestInfo != null) + { + v.add(new DERTaggedObject(false, 1, objectDigestInfo)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/V2TBSCertListGenerator.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/V2TBSCertListGenerator.java new file mode 100644 index 000000000..2435d46c9 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/V2TBSCertListGenerator.java @@ -0,0 +1,213 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.Vector; + +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERUTCTime; + +/** + * Generator for Version 2 TBSCertList structures. + *
+ *  TBSCertList  ::=  SEQUENCE  {
+ *       version                 Version OPTIONAL,
+ *                                    -- if present, shall be v2
+ *       signature               AlgorithmIdentifier,
+ *       issuer                  Name,
+ *       thisUpdate              Time,
+ *       nextUpdate              Time OPTIONAL,
+ *       revokedCertificates     SEQUENCE OF SEQUENCE  {
+ *            userCertificate         CertificateSerialNumber,
+ *            revocationDate          Time,
+ *            crlEntryExtensions      Extensions OPTIONAL
+ *                                          -- if present, shall be v2
+ *                                 }  OPTIONAL,
+ *       crlExtensions           [0]  EXPLICIT Extensions OPTIONAL
+ *                                          -- if present, shall be v2
+ *                                 }
+ * 
+ * + * Note: This class may be subject to change + */ +public class V2TBSCertListGenerator +{ + DERInteger version = new DERInteger(1); + + AlgorithmIdentifier signature; + X509Name issuer; + Time thisUpdate, nextUpdate=null; + X509Extensions extensions=null; + private Vector crlentries=null; + + public V2TBSCertListGenerator() + { + } + + + public void setSignature( + AlgorithmIdentifier signature) + { + this.signature = signature; + } + + public void setIssuer( + X509Name issuer) + { + this.issuer = issuer; + } + + public void setThisUpdate( + DERUTCTime thisUpdate) + { + this.thisUpdate = new Time(thisUpdate); + } + + public void setNextUpdate( + DERUTCTime nextUpdate) + { + this.nextUpdate = new Time(nextUpdate); + } + + public void setThisUpdate( + Time thisUpdate) + { + this.thisUpdate = thisUpdate; + } + + public void setNextUpdate( + Time nextUpdate) + { + this.nextUpdate = nextUpdate; + } + + public void addCRLEntry( + ASN1Sequence crlEntry) + { + if (crlentries == null) + { + crlentries = new Vector(); + } + + crlentries.addElement(crlEntry); + } + + public void addCRLEntry(DERInteger userCertificate, DERUTCTime revocationDate, int reason) + { + addCRLEntry(userCertificate, new Time(revocationDate), reason); + } + + public void addCRLEntry(DERInteger userCertificate, Time revocationDate, int reason) + { + addCRLEntry(userCertificate, revocationDate, reason, null); + } + + public void addCRLEntry(DERInteger userCertificate, Time revocationDate, int reason, DERGeneralizedTime invalidityDate) + { + Vector extOids = new Vector(); + Vector extValues = new Vector(); + + if (reason != 0) + { + CRLReason crlReason = new CRLReason(reason); + + try + { + extOids.addElement(X509Extensions.ReasonCode); + extValues.addElement(new X509Extension(false, new DEROctetString(crlReason.getEncoded()))); + } + catch (IOException e) + { + throw new IllegalArgumentException("error encoding reason: " + e); + } + } + + if (invalidityDate != null) + { + try + { + extOids.addElement(X509Extensions.InvalidityDate); + extValues.addElement(new X509Extension(false, new DEROctetString(invalidityDate.getEncoded()))); + } + catch (IOException e) + { + throw new IllegalArgumentException("error encoding invalidityDate: " + e); + } + } + + if (extOids.size() != 0) + { + addCRLEntry(userCertificate, revocationDate, new X509Extensions(extOids, extValues)); + } + else + { + addCRLEntry(userCertificate, revocationDate, null); + } + } + + public void addCRLEntry(DERInteger userCertificate, Time revocationDate, X509Extensions extensions) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(userCertificate); + v.add(revocationDate); + + if (extensions != null) + { + v.add(extensions); + } + + addCRLEntry(new DERSequence(v)); + } + + public void setExtensions( + X509Extensions extensions) + { + this.extensions = extensions; + } + + public TBSCertList generateTBSCertList() + { + if ((signature == null) || (issuer == null) || (thisUpdate == null)) + { + throw new IllegalStateException("Not all mandatory fields set in V2 TBSCertList generator."); + } + + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(signature); + v.add(issuer); + + v.add(thisUpdate); + if (nextUpdate != null) + { + v.add(nextUpdate); + } + + // Add CRLEntries if they exist + if (crlentries != null) + { + ASN1EncodableVector certs = new ASN1EncodableVector(); + Enumeration it = crlentries.elements(); + while(it.hasMoreElements()) + { + certs.add((ASN1Sequence)it.nextElement()); + } + v.add(new DERSequence(certs)); + } + + if (extensions != null) + { + v.add(new DERTaggedObject(0, extensions)); + } + + return new TBSCertList(new DERSequence(v)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/V3TBSCertificateGenerator.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/V3TBSCertificateGenerator.java new file mode 100644 index 000000000..e9b58c20f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/V3TBSCertificateGenerator.java @@ -0,0 +1,183 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERUTCTime; + +/** + * Generator for Version 3 TBSCertificateStructures. + *
+ * TBSCertificate ::= SEQUENCE {
+ *      version          [ 0 ]  Version DEFAULT v1(0),
+ *      serialNumber            CertificateSerialNumber,
+ *      signature               AlgorithmIdentifier,
+ *      issuer                  Name,
+ *      validity                Validity,
+ *      subject                 Name,
+ *      subjectPublicKeyInfo    SubjectPublicKeyInfo,
+ *      issuerUniqueID    [ 1 ] IMPLICIT UniqueIdentifier OPTIONAL,
+ *      subjectUniqueID   [ 2 ] IMPLICIT UniqueIdentifier OPTIONAL,
+ *      extensions        [ 3 ] Extensions OPTIONAL
+ *      }
+ * 
+ * + */ +public class V3TBSCertificateGenerator +{ + DERTaggedObject version = new DERTaggedObject(0, new DERInteger(2)); + + DERInteger serialNumber; + AlgorithmIdentifier signature; + X509Name issuer; + Time startDate, endDate; + X509Name subject; + SubjectPublicKeyInfo subjectPublicKeyInfo; + X509Extensions extensions; + + private boolean altNamePresentAndCritical; + private DERBitString issuerUniqueID; + private DERBitString subjectUniqueID; + + public V3TBSCertificateGenerator() + { + } + + public void setSerialNumber( + DERInteger serialNumber) + { + this.serialNumber = serialNumber; + } + + public void setSignature( + AlgorithmIdentifier signature) + { + this.signature = signature; + } + + public void setIssuer( + X509Name issuer) + { + this.issuer = issuer; + } + + public void setStartDate( + DERUTCTime startDate) + { + this.startDate = new Time(startDate); + } + + public void setStartDate( + Time startDate) + { + this.startDate = startDate; + } + + public void setEndDate( + DERUTCTime endDate) + { + this.endDate = new Time(endDate); + } + + public void setEndDate( + Time endDate) + { + this.endDate = endDate; + } + + public void setSubject( + X509Name subject) + { + this.subject = subject; + } + + public void setIssuerUniqueID( + DERBitString uniqueID) + { + this.issuerUniqueID = uniqueID; + } + + public void setSubjectUniqueID( + DERBitString uniqueID) + { + this.subjectUniqueID = uniqueID; + } + + public void setSubjectPublicKeyInfo( + SubjectPublicKeyInfo pubKeyInfo) + { + this.subjectPublicKeyInfo = pubKeyInfo; + } + + public void setExtensions( + X509Extensions extensions) + { + this.extensions = extensions; + if (extensions != null) + { + X509Extension altName = extensions.getExtension(X509Extensions.SubjectAlternativeName); + + if (altName != null && altName.isCritical()) + { + altNamePresentAndCritical = true; + } + } + } + + public TBSCertificateStructure generateTBSCertificate() + { + if ((serialNumber == null) || (signature == null) + || (issuer == null) || (startDate == null) || (endDate == null) + || (subject == null && !altNamePresentAndCritical) || (subjectPublicKeyInfo == null)) + { + throw new IllegalStateException("not all mandatory fields set in V3 TBScertificate generator"); + } + + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(version); + v.add(serialNumber); + v.add(signature); + v.add(issuer); + + // + // before and after dates + // + ASN1EncodableVector validity = new ASN1EncodableVector(); + + validity.add(startDate); + validity.add(endDate); + + v.add(new DERSequence(validity)); + + if (subject != null) + { + v.add(subject); + } + else + { + v.add(new DERSequence()); + } + + v.add(subjectPublicKeyInfo); + + if (issuerUniqueID != null) + { + v.add(new DERTaggedObject(false, 1, issuerUniqueID)); + } + + if (subjectUniqueID != null) + { + v.add(new DERTaggedObject(false, 2, subjectUniqueID)); + } + + if (extensions != null) + { + v.add(new DERTaggedObject(3, extensions)); + } + + return new TBSCertificateStructure(new DERSequence(v)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/X509Attributes.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509Attributes.java new file mode 100644 index 000000000..d4f4786a0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509Attributes.java @@ -0,0 +1,8 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public class X509Attributes +{ + public static final DERObjectIdentifier RoleSyntax = new DERObjectIdentifier("2.5.4.72"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/X509CertificateStructure.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509CertificateStructure.java new file mode 100644 index 000000000..3528dd695 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509CertificateStructure.java @@ -0,0 +1,132 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; + +/** + * an X509Certificate structure. + *
+ *  Certificate ::= SEQUENCE {
+ *      tbsCertificate          TBSCertificate,
+ *      signatureAlgorithm      AlgorithmIdentifier,
+ *      signature               BIT STRING
+ *  }
+ * 
+ */ +public class X509CertificateStructure + extends ASN1Encodable + implements X509ObjectIdentifiers, PKCSObjectIdentifiers +{ + ASN1Sequence seq; + TBSCertificateStructure tbsCert; + AlgorithmIdentifier sigAlgId; + DERBitString sig; + + public static X509CertificateStructure getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static X509CertificateStructure getInstance( + Object obj) + { + if (obj instanceof X509CertificateStructure) + { + return (X509CertificateStructure)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new X509CertificateStructure((ASN1Sequence)obj); + } + + if (obj != null) + { + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + throw new IllegalArgumentException("null object in factory"); + } + + public X509CertificateStructure( + ASN1Sequence seq) + { + this.seq = seq; + + // + // correct x509 certficate + // + if (seq.size() == 3) + { + tbsCert = TBSCertificateStructure.getInstance(seq.getObjectAt(0)); + sigAlgId = AlgorithmIdentifier.getInstance(seq.getObjectAt(1)); + + sig = DERBitString.getInstance(seq.getObjectAt(2)); + } + else + { + throw new IllegalArgumentException("sequence wrong size for a certificate"); + } + } + + public TBSCertificateStructure getTBSCertificate() + { + return tbsCert; + } + + public int getVersion() + { + return tbsCert.getVersion(); + } + + public DERInteger getSerialNumber() + { + return tbsCert.getSerialNumber(); + } + + public X509Name getIssuer() + { + return tbsCert.getIssuer(); + } + + public Time getStartDate() + { + return tbsCert.getStartDate(); + } + + public Time getEndDate() + { + return tbsCert.getEndDate(); + } + + public X509Name getSubject() + { + return tbsCert.getSubject(); + } + + public SubjectPublicKeyInfo getSubjectPublicKeyInfo() + { + return tbsCert.getSubjectPublicKeyInfo(); + } + + public AlgorithmIdentifier getSignatureAlgorithm() + { + return sigAlgId; + } + + public DERBitString getSignature() + { + return sig; + } + + public DERObject toASN1Object() + { + return seq; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/X509DefaultEntryConverter.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509DefaultEntryConverter.java new file mode 100644 index 000000000..df94cfba6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509DefaultEntryConverter.java @@ -0,0 +1,65 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERIA5String; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERPrintableString; +import com.google.bitcoin.bouncycastle.asn1.DERUTF8String; + +import java.io.IOException; + +/** + * The default converter for X509 DN entries when going from their + * string value to ASN.1 strings. + */ +public class X509DefaultEntryConverter + extends X509NameEntryConverter +{ + /** + * Apply default coversion for the given value depending on the oid + * and the character range of the value. + * + * @param oid the object identifier for the DN entry + * @param value the value associated with it + * @return the ASN.1 equivalent for the string value. + */ + public DERObject getConvertedValue( + DERObjectIdentifier oid, + String value) + { + if (value.length() != 0 && value.charAt(0) == '#') + { + try + { + return convertHexEncoded(value, 1); + } + catch (IOException e) + { + throw new RuntimeException("can't recode value for oid " + oid.getId()); + } + } + else + { + if (value.length() != 0 && value.charAt(0) == '\\') + { + value = value.substring(1); + } + if (oid.equals(X509Name.EmailAddress) || oid.equals(X509Name.DC)) + { + return new DERIA5String(value); + } + else if (oid.equals(X509Name.DATE_OF_BIRTH)) // accept time string as well as # (for compatibility) + { + return new DERGeneralizedTime(value); + } + else if (oid.equals(X509Name.C) || oid.equals(X509Name.SN) || oid.equals(X509Name.DN_QUALIFIER) + || oid.equals(X509Name.TELEPHONE_NUMBER)) + { + return new DERPrintableString(value); + } + } + + return new DERUTF8String(value); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/X509Extension.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509Extension.java new file mode 100644 index 000000000..a15128428 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509Extension.java @@ -0,0 +1,87 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Object; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.DERBoolean; + +import java.io.IOException; + +/** + * an object for the elements in the X.509 V3 extension block. + */ +public class X509Extension +{ + boolean critical; + ASN1OctetString value; + + public X509Extension( + DERBoolean critical, + ASN1OctetString value) + { + this.critical = critical.isTrue(); + this.value = value; + } + + public X509Extension( + boolean critical, + ASN1OctetString value) + { + this.critical = critical; + this.value = value; + } + + public boolean isCritical() + { + return critical; + } + + public ASN1OctetString getValue() + { + return value; + } + + public int hashCode() + { + if (this.isCritical()) + { + return this.getValue().hashCode(); + } + + + return ~this.getValue().hashCode(); + } + + public boolean equals( + Object o) + { + if (!(o instanceof X509Extension)) + { + return false; + } + + X509Extension other = (X509Extension)o; + + return other.getValue().equals(this.getValue()) + && (other.isCritical() == this.isCritical()); + } + + /** + * Convert the value of the passed in extension to an object + * @param ext the extension to parse + * @return the object the value string contains + * @exception IllegalArgumentException if conversion is not possible + */ + public static ASN1Object convertValueToObject( + X509Extension ext) + throws IllegalArgumentException + { + try + { + return ASN1Object.fromByteArray(ext.getValue().getOctets()); + } + catch (IOException e) + { + throw new IllegalArgumentException("can't convert extension: " + e); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/X509Extensions.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509Extensions.java new file mode 100644 index 000000000..92e7d0774 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509Extensions.java @@ -0,0 +1,393 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERBoolean; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +public class X509Extensions + extends ASN1Encodable +{ + /** + * Subject Directory Attributes + */ + public static final DERObjectIdentifier SubjectDirectoryAttributes = new DERObjectIdentifier("2.5.29.9"); + + /** + * Subject Key Identifier + */ + public static final DERObjectIdentifier SubjectKeyIdentifier = new DERObjectIdentifier("2.5.29.14"); + + /** + * Key Usage + */ + public static final DERObjectIdentifier KeyUsage = new DERObjectIdentifier("2.5.29.15"); + + /** + * Private Key Usage Period + */ + public static final DERObjectIdentifier PrivateKeyUsagePeriod = new DERObjectIdentifier("2.5.29.16"); + + /** + * Subject Alternative Name + */ + public static final DERObjectIdentifier SubjectAlternativeName = new DERObjectIdentifier("2.5.29.17"); + + /** + * Issuer Alternative Name + */ + public static final DERObjectIdentifier IssuerAlternativeName = new DERObjectIdentifier("2.5.29.18"); + + /** + * Basic Constraints + */ + public static final DERObjectIdentifier BasicConstraints = new DERObjectIdentifier("2.5.29.19"); + + /** + * CRL Number + */ + public static final DERObjectIdentifier CRLNumber = new DERObjectIdentifier("2.5.29.20"); + + /** + * Reason code + */ + public static final DERObjectIdentifier ReasonCode = new DERObjectIdentifier("2.5.29.21"); + + /** + * Hold Instruction Code + */ + public static final DERObjectIdentifier InstructionCode = new DERObjectIdentifier("2.5.29.23"); + + /** + * Invalidity Date + */ + public static final DERObjectIdentifier InvalidityDate = new DERObjectIdentifier("2.5.29.24"); + + /** + * Delta CRL indicator + */ + public static final DERObjectIdentifier DeltaCRLIndicator = new DERObjectIdentifier("2.5.29.27"); + + /** + * Issuing Distribution Point + */ + public static final DERObjectIdentifier IssuingDistributionPoint = new DERObjectIdentifier("2.5.29.28"); + + /** + * Certificate Issuer + */ + public static final DERObjectIdentifier CertificateIssuer = new DERObjectIdentifier("2.5.29.29"); + + /** + * Name Constraints + */ + public static final DERObjectIdentifier NameConstraints = new DERObjectIdentifier("2.5.29.30"); + + /** + * CRL Distribution Points + */ + public static final DERObjectIdentifier CRLDistributionPoints = new DERObjectIdentifier("2.5.29.31"); + + /** + * Certificate Policies + */ + public static final DERObjectIdentifier CertificatePolicies = new DERObjectIdentifier("2.5.29.32"); + + /** + * Policy Mappings + */ + public static final DERObjectIdentifier PolicyMappings = new DERObjectIdentifier("2.5.29.33"); + + /** + * Authority Key Identifier + */ + public static final DERObjectIdentifier AuthorityKeyIdentifier = new DERObjectIdentifier("2.5.29.35"); + + /** + * Policy Constraints + */ + public static final DERObjectIdentifier PolicyConstraints = new DERObjectIdentifier("2.5.29.36"); + + /** + * Extended Key Usage + */ + public static final DERObjectIdentifier ExtendedKeyUsage = new DERObjectIdentifier("2.5.29.37"); + + /** + * Freshest CRL + */ + public static final DERObjectIdentifier FreshestCRL = new DERObjectIdentifier("2.5.29.46"); + + /** + * Inhibit Any Policy + */ + public static final DERObjectIdentifier InhibitAnyPolicy = new DERObjectIdentifier("2.5.29.54"); + + /** + * Authority Info Access + */ + public static final DERObjectIdentifier AuthorityInfoAccess = new DERObjectIdentifier("1.3.6.1.5.5.7.1.1"); + + /** + * Subject Info Access + */ + public static final DERObjectIdentifier SubjectInfoAccess = new DERObjectIdentifier("1.3.6.1.5.5.7.1.11"); + + /** + * Logo Type + */ + public static final DERObjectIdentifier LogoType = new DERObjectIdentifier("1.3.6.1.5.5.7.1.12"); + + /** + * BiometricInfo + */ + public static final DERObjectIdentifier BiometricInfo = new DERObjectIdentifier("1.3.6.1.5.5.7.1.2"); + + /** + * QCStatements + */ + public static final DERObjectIdentifier QCStatements = new DERObjectIdentifier("1.3.6.1.5.5.7.1.3"); + + /** + * Audit identity extension in attribute certificates. + */ + public static final DERObjectIdentifier AuditIdentity = new DERObjectIdentifier("1.3.6.1.5.5.7.1.4"); + + /** + * NoRevAvail extension in attribute certificates. + */ + public static final DERObjectIdentifier NoRevAvail = new DERObjectIdentifier("2.5.29.56"); + + /** + * TargetInformation extension in attribute certificates. + */ + public static final DERObjectIdentifier TargetInformation = new DERObjectIdentifier("2.5.29.55"); + + private Hashtable extensions = new Hashtable(); + private Vector ordering = new Vector(); + + public static X509Extensions getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static X509Extensions getInstance( + Object obj) + { + if (obj == null || obj instanceof X509Extensions) + { + return (X509Extensions)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new X509Extensions((ASN1Sequence)obj); + } + + if (obj instanceof ASN1TaggedObject) + { + return getInstance(((ASN1TaggedObject)obj).getObject()); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * Constructor from ASN1Sequence. + * + * the extensions are a list of constructed sequences, either with (OID, OctetString) or (OID, Boolean, OctetString) + */ + public X509Extensions( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + while (e.hasMoreElements()) + { + ASN1Sequence s = ASN1Sequence.getInstance(e.nextElement()); + + if (s.size() == 3) + { + extensions.put(s.getObjectAt(0), new X509Extension(DERBoolean.getInstance(s.getObjectAt(1)), ASN1OctetString.getInstance(s.getObjectAt(2)))); + } + else if (s.size() == 2) + { + extensions.put(s.getObjectAt(0), new X509Extension(false, ASN1OctetString.getInstance(s.getObjectAt(1)))); + } + else + { + throw new IllegalArgumentException("Bad sequence size: " + s.size()); + } + + ordering.addElement(s.getObjectAt(0)); + } + } + + /** + * constructor from a table of extensions. + *

+ * it's is assumed the table contains OID/String pairs. + */ + public X509Extensions( + Hashtable extensions) + { + this(null, extensions); + } + + /** + * Constructor from a table of extensions with ordering. + *

+ * It's is assumed the table contains OID/String pairs. + */ + public X509Extensions( + Vector ordering, + Hashtable extensions) + { + Enumeration e; + + if (ordering == null) + { + e = extensions.keys(); + } + else + { + e = ordering.elements(); + } + + while (e.hasMoreElements()) + { + this.ordering.addElement(e.nextElement()); + } + + e = this.ordering.elements(); + + while (e.hasMoreElements()) + { + DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement(); + X509Extension ext = (X509Extension)extensions.get(oid); + + this.extensions.put(oid, ext); + } + } + + /** + * Constructor from two vectors + * + * @param objectIDs a vector of the object identifiers. + * @param values a vector of the extension values. + */ + public X509Extensions( + Vector objectIDs, + Vector values) + { + Enumeration e = objectIDs.elements(); + + while (e.hasMoreElements()) + { + this.ordering.addElement(e.nextElement()); + } + + int count = 0; + + e = this.ordering.elements(); + + while (e.hasMoreElements()) + { + DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement(); + X509Extension ext = (X509Extension)values.elementAt(count); + + this.extensions.put(oid, ext); + count++; + } + } + + /** + * return an Enumeration of the extension field's object ids. + */ + public Enumeration oids() + { + return ordering.elements(); + } + + /** + * return the extension represented by the object identifier + * passed in. + * + * @return the extension if it's present, null otherwise. + */ + public X509Extension getExtension( + DERObjectIdentifier oid) + { + return (X509Extension)extensions.get(oid); + } + + /** + *

+     *     Extensions        ::=   SEQUENCE SIZE (1..MAX) OF Extension
+     *
+     *     Extension         ::=   SEQUENCE {
+     *        extnId            EXTENSION.&id ({ExtensionSet}),
+     *        critical          BOOLEAN DEFAULT FALSE,
+     *        extnValue         OCTET STRING }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector vec = new ASN1EncodableVector(); + Enumeration e = ordering.elements(); + + while (e.hasMoreElements()) + { + DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement(); + X509Extension ext = (X509Extension)extensions.get(oid); + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(oid); + + if (ext.isCritical()) + { + v.add(new DERBoolean(true)); + } + + v.add(ext.getValue()); + + vec.add(new DERSequence(v)); + } + + return new DERSequence(vec); + } + + public boolean equivalent( + X509Extensions other) + { + if (extensions.size() != other.extensions.size()) + { + return false; + } + + Enumeration e1 = extensions.keys(); + + while (e1.hasMoreElements()) + { + Object key = e1.nextElement(); + + if (!extensions.get(key).equals(other.extensions.get(key))) + { + return false; + } + } + + return true; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/X509ExtensionsGenerator.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509ExtensionsGenerator.java new file mode 100644 index 000000000..bae57b9ef --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509ExtensionsGenerator.java @@ -0,0 +1,93 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; + +import java.io.IOException; +import java.util.Hashtable; +import java.util.Vector; + +/** + * Generator for X.509 extensions + */ +public class X509ExtensionsGenerator +{ + private Hashtable extensions = new Hashtable(); + private Vector extOrdering = new Vector(); + + /** + * Reset the generator + */ + public void reset() + { + extensions = new Hashtable(); + extOrdering = new Vector(); + } + + /** + * Add an extension with the given oid and the passed in value to be included + * in the OCTET STRING associated with the extension. + * + * @param oid OID for the extension. + * @param critical true if critical, false otherwise. + * @param value the ASN.1 object to be included in the extension. + */ + public void addExtension( + DERObjectIdentifier oid, + boolean critical, + DEREncodable value) + { + try + { + this.addExtension(oid, critical, value.getDERObject().getEncoded(ASN1Encodable.DER)); + } + catch (IOException e) + { + throw new IllegalArgumentException("error encoding value: " + e); + } + } + + /** + * Add an extension with the given oid and the passed in byte array to be wrapped in the + * OCTET STRING associated with the extension. + * + * @param oid OID for the extension. + * @param critical true if critical, false otherwise. + * @param value the byte array to be wrapped. + */ + public void addExtension( + DERObjectIdentifier oid, + boolean critical, + byte[] value) + { + if (extensions.containsKey(oid)) + { + throw new IllegalArgumentException("extension " + oid + " already added"); + } + + extOrdering.addElement(oid); + extensions.put(oid, new X509Extension(critical, new DEROctetString(value))); + } + + /** + * Return true if there are no extension present in this generator. + * + * @return true if empty, false otherwise + */ + public boolean isEmpty() + { + return extOrdering.isEmpty(); + } + + /** + * Generate an X509Extensions object based on the current state of the generator. + * + * @return an X09Extensions object. + */ + public X509Extensions generate() + { + return new X509Extensions(extOrdering, extensions); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/X509Name.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509Name.java new file mode 100644 index 000000000..f78224f41 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509Name.java @@ -0,0 +1,1256 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Object; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1Set; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERSet; +import com.google.bitcoin.bouncycastle.asn1.DERString; +import com.google.bitcoin.bouncycastle.asn1.DERUniversalString; +import com.google.bitcoin.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import com.google.bitcoin.bouncycastle.util.Strings; +import com.google.bitcoin.bouncycastle.util.encoders.Hex; + +/** + *
+ *     RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+ *
+ *     RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
+ *
+ *     AttributeTypeAndValue ::= SEQUENCE {
+ *                                   type  OBJECT IDENTIFIER,
+ *                                   value ANY }
+ * 
+ */ +public class X509Name + extends ASN1Encodable +{ + /** + * country code - StringType(SIZE(2)) + */ + public static final DERObjectIdentifier C = new DERObjectIdentifier("2.5.4.6"); + + /** + * organization - StringType(SIZE(1..64)) + */ + public static final DERObjectIdentifier O = new DERObjectIdentifier("2.5.4.10"); + + /** + * organizational unit name - StringType(SIZE(1..64)) + */ + public static final DERObjectIdentifier OU = new DERObjectIdentifier("2.5.4.11"); + + /** + * Title + */ + public static final DERObjectIdentifier T = new DERObjectIdentifier("2.5.4.12"); + + /** + * common name - StringType(SIZE(1..64)) + */ + public static final DERObjectIdentifier CN = new DERObjectIdentifier("2.5.4.3"); + + /** + * device serial number name - StringType(SIZE(1..64)) + */ + public static final DERObjectIdentifier SN = new DERObjectIdentifier("2.5.4.5"); + + /** + * street - StringType(SIZE(1..64)) + */ + public static final DERObjectIdentifier STREET = new DERObjectIdentifier("2.5.4.9"); + + /** + * device serial number name - StringType(SIZE(1..64)) + */ + public static final DERObjectIdentifier SERIALNUMBER = SN; + + /** + * locality name - StringType(SIZE(1..64)) + */ + public static final DERObjectIdentifier L = new DERObjectIdentifier("2.5.4.7"); + + /** + * state, or province name - StringType(SIZE(1..64)) + */ + public static final DERObjectIdentifier ST = new DERObjectIdentifier("2.5.4.8"); + + /** + * Naming attributes of type X520name + */ + public static final DERObjectIdentifier SURNAME = new DERObjectIdentifier("2.5.4.4"); + public static final DERObjectIdentifier GIVENNAME = new DERObjectIdentifier("2.5.4.42"); + public static final DERObjectIdentifier INITIALS = new DERObjectIdentifier("2.5.4.43"); + public static final DERObjectIdentifier GENERATION = new DERObjectIdentifier("2.5.4.44"); + public static final DERObjectIdentifier UNIQUE_IDENTIFIER = new DERObjectIdentifier("2.5.4.45"); + + /** + * businessCategory - DirectoryString(SIZE(1..128) + */ + public static final DERObjectIdentifier BUSINESS_CATEGORY = new DERObjectIdentifier( + "2.5.4.15"); + + /** + * postalCode - DirectoryString(SIZE(1..40) + */ + public static final DERObjectIdentifier POSTAL_CODE = new DERObjectIdentifier( + "2.5.4.17"); + + /** + * dnQualifier - DirectoryString(SIZE(1..64) + */ + public static final DERObjectIdentifier DN_QUALIFIER = new DERObjectIdentifier( + "2.5.4.46"); + + /** + * RFC 3039 Pseudonym - DirectoryString(SIZE(1..64) + */ + public static final DERObjectIdentifier PSEUDONYM = new DERObjectIdentifier( + "2.5.4.65"); + + + /** + * RFC 3039 DateOfBirth - GeneralizedTime - YYYYMMDD000000Z + */ + public static final DERObjectIdentifier DATE_OF_BIRTH = new DERObjectIdentifier( + "1.3.6.1.5.5.7.9.1"); + + /** + * RFC 3039 PlaceOfBirth - DirectoryString(SIZE(1..128) + */ + public static final DERObjectIdentifier PLACE_OF_BIRTH = new DERObjectIdentifier( + "1.3.6.1.5.5.7.9.2"); + + /** + * RFC 3039 Gender - PrintableString (SIZE(1)) -- "M", "F", "m" or "f" + */ + public static final DERObjectIdentifier GENDER = new DERObjectIdentifier( + "1.3.6.1.5.5.7.9.3"); + + /** + * RFC 3039 CountryOfCitizenship - PrintableString (SIZE (2)) -- ISO 3166 + * codes only + */ + public static final DERObjectIdentifier COUNTRY_OF_CITIZENSHIP = new DERObjectIdentifier( + "1.3.6.1.5.5.7.9.4"); + + /** + * RFC 3039 CountryOfResidence - PrintableString (SIZE (2)) -- ISO 3166 + * codes only + */ + public static final DERObjectIdentifier COUNTRY_OF_RESIDENCE = new DERObjectIdentifier( + "1.3.6.1.5.5.7.9.5"); + + + /** + * ISIS-MTT NameAtBirth - DirectoryString(SIZE(1..64) + */ + public static final DERObjectIdentifier NAME_AT_BIRTH = new DERObjectIdentifier("1.3.36.8.3.14"); + + /** + * RFC 3039 PostalAddress - SEQUENCE SIZE (1..6) OF + * DirectoryString(SIZE(1..30)) + */ + public static final DERObjectIdentifier POSTAL_ADDRESS = new DERObjectIdentifier("2.5.4.16"); + + /** + * RFC 2256 dmdName + */ + public static final DERObjectIdentifier DMD_NAME = new DERObjectIdentifier("2.5.4.54"); + + /** + * id-at-telephoneNumber + */ + public static final DERObjectIdentifier TELEPHONE_NUMBER = X509ObjectIdentifiers.id_at_telephoneNumber; + + /** + * id-at-name + */ + public static final DERObjectIdentifier NAME = X509ObjectIdentifiers.id_at_name; + + /** + * Email address (RSA PKCS#9 extension) - IA5String. + *

Note: if you're trying to be ultra orthodox, don't use this! It shouldn't be in here. + */ + public static final DERObjectIdentifier EmailAddress = PKCSObjectIdentifiers.pkcs_9_at_emailAddress; + + /** + * more from PKCS#9 + */ + public static final DERObjectIdentifier UnstructuredName = PKCSObjectIdentifiers.pkcs_9_at_unstructuredName; + public static final DERObjectIdentifier UnstructuredAddress = PKCSObjectIdentifiers.pkcs_9_at_unstructuredAddress; + + /** + * email address in Verisign certificates + */ + public static final DERObjectIdentifier E = EmailAddress; + + /* + * others... + */ + public static final DERObjectIdentifier DC = new DERObjectIdentifier("0.9.2342.19200300.100.1.25"); + + /** + * LDAP User id. + */ + public static final DERObjectIdentifier UID = new DERObjectIdentifier("0.9.2342.19200300.100.1.1"); + + /** + * determines whether or not strings should be processed and printed + * from back to front. + */ + public static boolean DefaultReverse = false; + + /** + * default look up table translating OID values into their common symbols following + * the convention in RFC 2253 with a few extras + */ + public static final Hashtable DefaultSymbols = new Hashtable(); + + /** + * look up table translating OID values into their common symbols following the convention in RFC 2253 + * + */ + public static final Hashtable RFC2253Symbols = new Hashtable(); + + /** + * look up table translating OID values into their common symbols following the convention in RFC 1779 + * + */ + public static final Hashtable RFC1779Symbols = new Hashtable(); + + /** + * look up table translating common symbols into their OIDS. + */ + public static final Hashtable DefaultLookUp = new Hashtable(); + + /** + * look up table translating OID values into their common symbols + * @deprecated use DefaultSymbols + */ + public static final Hashtable OIDLookUp = DefaultSymbols; + + /** + * look up table translating string values into their OIDS - + * @deprecated use DefaultLookUp + */ + public static final Hashtable SymbolLookUp = DefaultLookUp; + + private static final Boolean TRUE = new Boolean(true); // for J2ME compatibility + private static final Boolean FALSE = new Boolean(false); + + static + { + DefaultSymbols.put(C, "C"); + DefaultSymbols.put(O, "O"); + DefaultSymbols.put(T, "T"); + DefaultSymbols.put(OU, "OU"); + DefaultSymbols.put(CN, "CN"); + DefaultSymbols.put(L, "L"); + DefaultSymbols.put(ST, "ST"); + DefaultSymbols.put(SN, "SERIALNUMBER"); + DefaultSymbols.put(EmailAddress, "E"); + DefaultSymbols.put(DC, "DC"); + DefaultSymbols.put(UID, "UID"); + DefaultSymbols.put(STREET, "STREET"); + DefaultSymbols.put(SURNAME, "SURNAME"); + DefaultSymbols.put(GIVENNAME, "GIVENNAME"); + DefaultSymbols.put(INITIALS, "INITIALS"); + DefaultSymbols.put(GENERATION, "GENERATION"); + DefaultSymbols.put(UnstructuredAddress, "unstructuredAddress"); + DefaultSymbols.put(UnstructuredName, "unstructuredName"); + DefaultSymbols.put(UNIQUE_IDENTIFIER, "UniqueIdentifier"); + DefaultSymbols.put(DN_QUALIFIER, "DN"); + DefaultSymbols.put(PSEUDONYM, "Pseudonym"); + DefaultSymbols.put(POSTAL_ADDRESS, "PostalAddress"); + DefaultSymbols.put(NAME_AT_BIRTH, "NameAtBirth"); + DefaultSymbols.put(COUNTRY_OF_CITIZENSHIP, "CountryOfCitizenship"); + DefaultSymbols.put(COUNTRY_OF_RESIDENCE, "CountryOfResidence"); + DefaultSymbols.put(GENDER, "Gender"); + DefaultSymbols.put(PLACE_OF_BIRTH, "PlaceOfBirth"); + DefaultSymbols.put(DATE_OF_BIRTH, "DateOfBirth"); + DefaultSymbols.put(POSTAL_CODE, "PostalCode"); + DefaultSymbols.put(BUSINESS_CATEGORY, "BusinessCategory"); + DefaultSymbols.put(TELEPHONE_NUMBER, "TelephoneNumber"); + DefaultSymbols.put(NAME, "Name"); + + RFC2253Symbols.put(C, "C"); + RFC2253Symbols.put(O, "O"); + RFC2253Symbols.put(OU, "OU"); + RFC2253Symbols.put(CN, "CN"); + RFC2253Symbols.put(L, "L"); + RFC2253Symbols.put(ST, "ST"); + RFC2253Symbols.put(STREET, "STREET"); + RFC2253Symbols.put(DC, "DC"); + RFC2253Symbols.put(UID, "UID"); + + RFC1779Symbols.put(C, "C"); + RFC1779Symbols.put(O, "O"); + RFC1779Symbols.put(OU, "OU"); + RFC1779Symbols.put(CN, "CN"); + RFC1779Symbols.put(L, "L"); + RFC1779Symbols.put(ST, "ST"); + RFC1779Symbols.put(STREET, "STREET"); + + DefaultLookUp.put("c", C); + DefaultLookUp.put("o", O); + DefaultLookUp.put("t", T); + DefaultLookUp.put("ou", OU); + DefaultLookUp.put("cn", CN); + DefaultLookUp.put("l", L); + DefaultLookUp.put("st", ST); + DefaultLookUp.put("sn", SN); + DefaultLookUp.put("serialnumber", SN); + DefaultLookUp.put("street", STREET); + DefaultLookUp.put("emailaddress", E); + DefaultLookUp.put("dc", DC); + DefaultLookUp.put("e", E); + DefaultLookUp.put("uid", UID); + DefaultLookUp.put("surname", SURNAME); + DefaultLookUp.put("givenname", GIVENNAME); + DefaultLookUp.put("initials", INITIALS); + DefaultLookUp.put("generation", GENERATION); + DefaultLookUp.put("unstructuredaddress", UnstructuredAddress); + DefaultLookUp.put("unstructuredname", UnstructuredName); + DefaultLookUp.put("uniqueidentifier", UNIQUE_IDENTIFIER); + DefaultLookUp.put("dn", DN_QUALIFIER); + DefaultLookUp.put("pseudonym", PSEUDONYM); + DefaultLookUp.put("postaladdress", POSTAL_ADDRESS); + DefaultLookUp.put("nameofbirth", NAME_AT_BIRTH); + DefaultLookUp.put("countryofcitizenship", COUNTRY_OF_CITIZENSHIP); + DefaultLookUp.put("countryofresidence", COUNTRY_OF_RESIDENCE); + DefaultLookUp.put("gender", GENDER); + DefaultLookUp.put("placeofbirth", PLACE_OF_BIRTH); + DefaultLookUp.put("dateofbirth", DATE_OF_BIRTH); + DefaultLookUp.put("postalcode", POSTAL_CODE); + DefaultLookUp.put("businesscategory", BUSINESS_CATEGORY); + DefaultLookUp.put("telephonenumber", TELEPHONE_NUMBER); + DefaultLookUp.put("name", NAME); + } + + private X509NameEntryConverter converter = null; + private Vector ordering = new Vector(); + private Vector values = new Vector(); + private Vector added = new Vector(); + + private ASN1Sequence seq; + + private boolean isHashCodeCalculated; + private int hashCodeValue; + + /** + * Return a X509Name based on the passed in tagged object. + * + * @param obj tag object holding name. + * @param explicit true if explicitly tagged false otherwise. + * @return the X509Name + */ + public static X509Name getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(ASN1Sequence.getInstance(obj, explicit)); + } + + public static X509Name getInstance( + Object obj) + { + if (obj == null || obj instanceof X509Name) + { + return (X509Name)obj; + } + else if (obj instanceof ASN1Sequence) + { + return new X509Name((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("unknown object in factory: " + obj.getClass().getName()); + } + + /** + * Constructor from ASN1Sequence + * + * the principal will be a list of constructed sets, each containing an (OID, String) pair. + */ + public X509Name( + ASN1Sequence seq) + { + this.seq = seq; + + Enumeration e = seq.getObjects(); + + while (e.hasMoreElements()) + { + ASN1Set set = ASN1Set.getInstance(e.nextElement()); + + for (int i = 0; i < set.size(); i++) + { + ASN1Sequence s = ASN1Sequence.getInstance(set.getObjectAt(i)); + + if (s.size() != 2) + { + throw new IllegalArgumentException("badly sized pair"); + } + + ordering.addElement(DERObjectIdentifier.getInstance(s.getObjectAt(0))); + + DEREncodable value = s.getObjectAt(1); + if (value instanceof DERString && !(value instanceof DERUniversalString)) + { + String v = ((DERString)value).getString(); + if (v.length() > 0 && v.charAt(0) == '#') + { + values.addElement("\\" + v); + } + else + { + values.addElement(v); + } + } + else + { + values.addElement("#" + bytesToString(Hex.encode(value.getDERObject().getDEREncoded()))); + } + added.addElement((i != 0) ? TRUE : FALSE); // to allow earlier JDK compatibility + } + } + } + + /** + * constructor from a table of attributes. + *

+ * it's is assumed the table contains OID/String pairs, and the contents + * of the table are copied into an internal table as part of the + * construction process. + *

+ * Note: if the name you are trying to generate should be + * following a specific ordering, you should use the constructor + * with the ordering specified below. + * @deprecated use an ordered constructor! The hashtable ordering is rarely correct + */ + public X509Name( + Hashtable attributes) + { + this(null, attributes); + } + + /** + * Constructor from a table of attributes with ordering. + *

+ * it's is assumed the table contains OID/String pairs, and the contents + * of the table are copied into an internal table as part of the + * construction process. The ordering vector should contain the OIDs + * in the order they are meant to be encoded or printed in toString. + */ + public X509Name( + Vector ordering, + Hashtable attributes) + { + this(ordering, attributes, new X509DefaultEntryConverter()); + } + + /** + * Constructor from a table of attributes with ordering. + *

+ * it's is assumed the table contains OID/String pairs, and the contents + * of the table are copied into an internal table as part of the + * construction process. The ordering vector should contain the OIDs + * in the order they are meant to be encoded or printed in toString. + *

+ * The passed in converter will be used to convert the strings into their + * ASN.1 counterparts. + */ + public X509Name( + Vector ordering, + Hashtable attributes, + X509NameEntryConverter converter) + { + this.converter = converter; + + if (ordering != null) + { + for (int i = 0; i != ordering.size(); i++) + { + this.ordering.addElement(ordering.elementAt(i)); + this.added.addElement(FALSE); + } + } + else + { + Enumeration e = attributes.keys(); + + while (e.hasMoreElements()) + { + this.ordering.addElement(e.nextElement()); + this.added.addElement(FALSE); + } + } + + for (int i = 0; i != this.ordering.size(); i++) + { + DERObjectIdentifier oid = (DERObjectIdentifier)this.ordering.elementAt(i); + + if (attributes.get(oid) == null) + { + throw new IllegalArgumentException("No attribute for object id - " + oid.getId() + " - passed to distinguished name"); + } + + this.values.addElement(attributes.get(oid)); // copy the hash table + } + } + + /** + * Takes two vectors one of the oids and the other of the values. + */ + public X509Name( + Vector oids, + Vector values) + { + this(oids, values, new X509DefaultEntryConverter()); + } + + /** + * Takes two vectors one of the oids and the other of the values. + *

+ * The passed in converter will be used to convert the strings into their + * ASN.1 counterparts. + */ + public X509Name( + Vector oids, + Vector values, + X509NameEntryConverter converter) + { + this.converter = converter; + + if (oids.size() != values.size()) + { + throw new IllegalArgumentException("oids vector must be same length as values."); + } + + for (int i = 0; i < oids.size(); i++) + { + this.ordering.addElement(oids.elementAt(i)); + this.values.addElement(values.elementAt(i)); + this.added.addElement(FALSE); + } + } + +// private Boolean isEncoded(String s) +// { +// if (s.charAt(0) == '#') +// { +// return TRUE; +// } +// +// return FALSE; +// } + + /** + * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + * some such, converting it into an ordered set of name attributes. + */ + public X509Name( + String dirName) + { + this(DefaultReverse, DefaultLookUp, dirName); + } + + /** + * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + * some such, converting it into an ordered set of name attributes with each + * string value being converted to its associated ASN.1 type using the passed + * in converter. + */ + public X509Name( + String dirName, + X509NameEntryConverter converter) + { + this(DefaultReverse, DefaultLookUp, dirName, converter); + } + + /** + * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + * some such, converting it into an ordered set of name attributes. If reverse + * is true, create the encoded version of the sequence starting from the + * last element in the string. + */ + public X509Name( + boolean reverse, + String dirName) + { + this(reverse, DefaultLookUp, dirName); + } + + /** + * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + * some such, converting it into an ordered set of name attributes with each + * string value being converted to its associated ASN.1 type using the passed + * in converter. If reverse is true the ASN.1 sequence representing the DN will + * be built by starting at the end of the string, rather than the start. + */ + public X509Name( + boolean reverse, + String dirName, + X509NameEntryConverter converter) + { + this(reverse, DefaultLookUp, dirName, converter); + } + + /** + * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + * some such, converting it into an ordered set of name attributes. lookUp + * should provide a table of lookups, indexed by lowercase only strings and + * yielding a DERObjectIdentifier, other than that OID. and numeric oids + * will be processed automatically. + *
+ * If reverse is true, create the encoded version of the sequence + * starting from the last element in the string. + * @param reverse true if we should start scanning from the end (RFC 2553). + * @param lookUp table of names and their oids. + * @param dirName the X.500 string to be parsed. + */ + public X509Name( + boolean reverse, + Hashtable lookUp, + String dirName) + { + this(reverse, lookUp, dirName, new X509DefaultEntryConverter()); + } + + private DERObjectIdentifier decodeOID( + String name, + Hashtable lookUp) + { + if (Strings.toUpperCase(name).startsWith("OID.")) + { + return new DERObjectIdentifier(name.substring(4)); + } + else if (name.charAt(0) >= '0' && name.charAt(0) <= '9') + { + return new DERObjectIdentifier(name); + } + + DERObjectIdentifier oid = (DERObjectIdentifier)lookUp.get(Strings.toLowerCase(name)); + if (oid == null) + { + throw new IllegalArgumentException("Unknown object id - " + name + " - passed to distinguished name"); + } + + return oid; + } + + /** + * Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + * some such, converting it into an ordered set of name attributes. lookUp + * should provide a table of lookups, indexed by lowercase only strings and + * yielding a DERObjectIdentifier, other than that OID. and numeric oids + * will be processed automatically. The passed in converter is used to convert the + * string values to the right of each equals sign to their ASN.1 counterparts. + *
+ * @param reverse true if we should start scanning from the end, false otherwise. + * @param lookUp table of names and oids. + * @param dirName the string dirName + * @param converter the converter to convert string values into their ASN.1 equivalents + */ + public X509Name( + boolean reverse, + Hashtable lookUp, + String dirName, + X509NameEntryConverter converter) + { + this.converter = converter; + X509NameTokenizer nTok = new X509NameTokenizer(dirName); + + while (nTok.hasMoreTokens()) + { + String token = nTok.nextToken(); + int index = token.indexOf('='); + + if (index == -1) + { + throw new IllegalArgumentException("badly formated directory string"); + } + + String name = token.substring(0, index); + String value = token.substring(index + 1); + DERObjectIdentifier oid = decodeOID(name, lookUp); + + if (value.indexOf('+') > 0) + { + X509NameTokenizer vTok = new X509NameTokenizer(value, '+'); + String v = vTok.nextToken(); + + this.ordering.addElement(oid); + this.values.addElement(v); + this.added.addElement(FALSE); + + while (vTok.hasMoreTokens()) + { + String sv = vTok.nextToken(); + int ndx = sv.indexOf('='); + + String nm = sv.substring(0, ndx); + String vl = sv.substring(ndx + 1); + this.ordering.addElement(decodeOID(nm, lookUp)); + this.values.addElement(vl); + this.added.addElement(TRUE); + } + } + else + { + this.ordering.addElement(oid); + this.values.addElement(value); + this.added.addElement(FALSE); + } + } + + if (reverse) + { + Vector o = new Vector(); + Vector v = new Vector(); + Vector a = new Vector(); + + int count = 1; + + for (int i = 0; i < this.ordering.size(); i++) + { + if (((Boolean)this.added.elementAt(i)).booleanValue()) + { + o.insertElementAt(this.ordering.elementAt(i), count); + v.insertElementAt(this.values.elementAt(i), count); + a.insertElementAt(this.added.elementAt(i), count); + count++; + } + else + { + o.insertElementAt(this.ordering.elementAt(i), 0); + v.insertElementAt(this.values.elementAt(i), 0); + a.insertElementAt(this.added.elementAt(i), 0); + count = 1; + } + } + + this.ordering = o; + this.values = v; + this.added = a; + } + } + + /** + * return a vector of the oids in the name, in the order they were found. + */ + public Vector getOIDs() + { + Vector v = new Vector(); + + for (int i = 0; i != ordering.size(); i++) + { + v.addElement(ordering.elementAt(i)); + } + + return v; + } + + /** + * return a vector of the values found in the name, in the order they + * were found. + */ + public Vector getValues() + { + Vector v = new Vector(); + + for (int i = 0; i != values.size(); i++) + { + v.addElement(values.elementAt(i)); + } + + return v; + } + + /** + * return a vector of the values found in the name, in the order they + * were found, with the DN label corresponding to passed in oid. + */ + public Vector getValues( + DERObjectIdentifier oid) + { + Vector v = new Vector(); + + for (int i = 0; i != values.size(); i++) + { + if (ordering.elementAt(i).equals(oid)) + { + String val = (String)values.elementAt(i); + + if (val.length() > 2 && val.charAt(0) == '\\' && val.charAt(1) == '#') + { + v.addElement(val.substring(1)); + } + else + { + v.addElement(val); + } + } + } + + return v; + } + + public DERObject toASN1Object() + { + if (seq == null) + { + ASN1EncodableVector vec = new ASN1EncodableVector(); + ASN1EncodableVector sVec = new ASN1EncodableVector(); + DERObjectIdentifier lstOid = null; + + for (int i = 0; i != ordering.size(); i++) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + DERObjectIdentifier oid = (DERObjectIdentifier)ordering.elementAt(i); + + v.add(oid); + + String str = (String)values.elementAt(i); + + v.add(converter.getConvertedValue(oid, str)); + + if (lstOid == null + || ((Boolean)this.added.elementAt(i)).booleanValue()) + { + sVec.add(new DERSequence(v)); + } + else + { + vec.add(new DERSet(sVec)); + sVec = new ASN1EncodableVector(); + + sVec.add(new DERSequence(v)); + } + + lstOid = oid; + } + + vec.add(new DERSet(sVec)); + + seq = new DERSequence(vec); + } + + return seq; + } + + /** + * @param inOrder if true the order of both X509 names must be the same, + * as well as the values associated with each element. + */ + public boolean equals(Object obj, boolean inOrder) + { + if (!inOrder) + { + return this.equals(obj); + } + + if (obj == this) + { + return true; + } + + if (!(obj instanceof X509Name || obj instanceof ASN1Sequence)) + { + return false; + } + + DERObject derO = ((DEREncodable)obj).getDERObject(); + + if (this.getDERObject().equals(derO)) + { + return true; + } + + X509Name other; + + try + { + other = X509Name.getInstance(obj); + } + catch (IllegalArgumentException e) + { + return false; + } + + int orderingSize = ordering.size(); + + if (orderingSize != other.ordering.size()) + { + return false; + } + + for (int i = 0; i < orderingSize; i++) + { + DERObjectIdentifier oid = (DERObjectIdentifier)ordering.elementAt(i); + DERObjectIdentifier oOid = (DERObjectIdentifier)other.ordering.elementAt(i); + + if (oid.equals(oOid)) + { + String value = (String)values.elementAt(i); + String oValue = (String)other.values.elementAt(i); + + if (!equivalentStrings(value, oValue)) + { + return false; + } + } + else + { + return false; + } + } + + return true; + } + + public int hashCode() + { + if (isHashCodeCalculated) + { + return hashCodeValue; + } + + isHashCodeCalculated = true; + + // this needs to be order independent, like equals + for (int i = 0; i != ordering.size(); i += 1) + { + String value = (String)values.elementAt(i); + + value = canonicalize(value); + value = stripInternalSpaces(value); + + hashCodeValue ^= value.hashCode(); + } + + return hashCodeValue; + } + + /** + * test for equality - note: case is ignored. + */ + public boolean equals(Object obj) + { + if (obj == this) + { + return true; + } + + if (!(obj instanceof X509Name || obj instanceof ASN1Sequence)) + { + return false; + } + + DERObject derO = ((DEREncodable)obj).getDERObject(); + + if (this.getDERObject().equals(derO)) + { + return true; + } + + X509Name other; + + try + { + other = X509Name.getInstance(obj); + } + catch (IllegalArgumentException e) + { + return false; + } + + int orderingSize = ordering.size(); + + if (orderingSize != other.ordering.size()) + { + return false; + } + + boolean[] indexes = new boolean[orderingSize]; + int start, end, delta; + + if (ordering.elementAt(0).equals(other.ordering.elementAt(0))) // guess forward + { + start = 0; + end = orderingSize; + delta = 1; + } + else // guess reversed - most common problem + { + start = orderingSize - 1; + end = -1; + delta = -1; + } + + for (int i = start; i != end; i += delta) + { + boolean found = false; + DERObjectIdentifier oid = (DERObjectIdentifier)ordering.elementAt(i); + String value = (String)values.elementAt(i); + + for (int j = 0; j < orderingSize; j++) + { + if (indexes[j]) + { + continue; + } + + DERObjectIdentifier oOid = (DERObjectIdentifier)other.ordering.elementAt(j); + + if (oid.equals(oOid)) + { + String oValue = (String)other.values.elementAt(j); + + if (equivalentStrings(value, oValue)) + { + indexes[j] = true; + found = true; + break; + } + } + } + + if (!found) + { + return false; + } + } + + return true; + } + + private boolean equivalentStrings(String s1, String s2) + { + String value = canonicalize(s1); + String oValue = canonicalize(s2); + + if (!value.equals(oValue)) + { + value = stripInternalSpaces(value); + oValue = stripInternalSpaces(oValue); + + if (!value.equals(oValue)) + { + return false; + } + } + + return true; + } + + private String canonicalize(String s) + { + String value = Strings.toLowerCase(s.trim()); + + if (value.length() > 0 && value.charAt(0) == '#') + { + DERObject obj = decodeObject(value); + + if (obj instanceof DERString) + { + value = Strings.toLowerCase(((DERString)obj).getString().trim()); + } + } + + return value; + } + + private ASN1Object decodeObject(String oValue) + { + try + { + return ASN1Object.fromByteArray(Hex.decode(oValue.substring(1))); + } + catch (IOException e) + { + throw new IllegalStateException("unknown encoding in name: " + e); + } + } + + private String stripInternalSpaces( + String str) + { + StringBuffer res = new StringBuffer(); + + if (str.length() != 0) + { + char c1 = str.charAt(0); + + res.append(c1); + + for (int k = 1; k < str.length(); k++) + { + char c2 = str.charAt(k); + if (!(c1 == ' ' && c2 == ' ')) + { + res.append(c2); + } + c1 = c2; + } + } + + return res.toString(); + } + + private void appendValue( + StringBuffer buf, + Hashtable oidSymbols, + DERObjectIdentifier oid, + String value) + { + String sym = (String)oidSymbols.get(oid); + + if (sym != null) + { + buf.append(sym); + } + else + { + buf.append(oid.getId()); + } + + buf.append('='); + + int index = buf.length(); + + buf.append(value); + + int end = buf.length(); + + if (value.length() >= 2 && value.charAt(0) == '\\' && value.charAt(1) == '#') + { + index += 2; + } + + while (index != end) + { + if ((buf.charAt(index) == ',') + || (buf.charAt(index) == '"') + || (buf.charAt(index) == '\\') + || (buf.charAt(index) == '+') + || (buf.charAt(index) == '=') + || (buf.charAt(index) == '<') + || (buf.charAt(index) == '>') + || (buf.charAt(index) == ';')) + { + buf.insert(index, "\\"); + index++; + end++; + } + + index++; + } + } + + /** + * convert the structure to a string - if reverse is true the + * oids and values are listed out starting with the last element + * in the sequence (ala RFC 2253), otherwise the string will begin + * with the first element of the structure. If no string definition + * for the oid is found in oidSymbols the string value of the oid is + * added. Two standard symbol tables are provided DefaultSymbols, and + * RFC2253Symbols as part of this class. + * + * @param reverse if true start at the end of the sequence and work back. + * @param oidSymbols look up table strings for oids. + */ + public String toString( + boolean reverse, + Hashtable oidSymbols) + { + StringBuffer buf = new StringBuffer(); + Vector components = new Vector(); + boolean first = true; + + StringBuffer ava = null; + + for (int i = 0; i < ordering.size(); i++) + { + if (((Boolean)added.elementAt(i)).booleanValue()) + { + ava.append('+'); + appendValue(ava, oidSymbols, + (DERObjectIdentifier)ordering.elementAt(i), + (String)values.elementAt(i)); + } + else + { + ava = new StringBuffer(); + appendValue(ava, oidSymbols, + (DERObjectIdentifier)ordering.elementAt(i), + (String)values.elementAt(i)); + components.addElement(ava); + } + } + + if (reverse) + { + for (int i = components.size() - 1; i >= 0; i--) + { + if (first) + { + first = false; + } + else + { + buf.append(','); + } + + buf.append(components.elementAt(i).toString()); + } + } + else + { + for (int i = 0; i < components.size(); i++) + { + if (first) + { + first = false; + } + else + { + buf.append(','); + } + + buf.append(components.elementAt(i).toString()); + } + } + + return buf.toString(); + } + + private String bytesToString( + byte[] data) + { + char[] cs = new char[data.length]; + + for (int i = 0; i != cs.length; i++) + { + cs[i] = (char)(data[i] & 0xff); + } + + return new String(cs); + } + + public String toString() + { + return toString(DefaultReverse, DefaultSymbols); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/X509NameEntryConverter.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509NameEntryConverter.java new file mode 100644 index 000000000..a8b6a6b3f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509NameEntryConverter.java @@ -0,0 +1,113 @@ +package com.google.bitcoin.bouncycastle.asn1.x509; + +import com.google.bitcoin.bouncycastle.asn1.ASN1InputStream; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERPrintableString; +import com.google.bitcoin.bouncycastle.util.Strings; + +import java.io.IOException; + +/** + * It turns out that the number of standard ways the fields in a DN should be + * encoded into their ASN.1 counterparts is rapidly approaching the + * number of machines on the internet. By default the X509Name class + * will produce UTF8Strings in line with the current recommendations (RFC 3280). + *

+ * An example of an encoder look like below: + *

+ * public class X509DirEntryConverter
+ *     extends X509NameEntryConverter
+ * {
+ *     public DERObject getConvertedValue(
+ *         DERObjectIdentifier  oid,
+ *         String               value)
+ *     {
+ *         if (str.length() != 0 && str.charAt(0) == '#')
+ *         {
+ *             return convertHexEncoded(str, 1);
+ *         }
+ *         if (oid.equals(EmailAddress))
+ *         {
+ *             return new DERIA5String(str);
+ *         }
+ *         else if (canBePrintable(str))
+ *         {
+ *             return new DERPrintableString(str);
+ *         }
+ *         else if (canBeUTF8(str))
+ *         {
+ *             return new DERUTF8String(str);
+ *         }
+ *         else
+ *         {
+ *             return new DERBMPString(str);
+ *         }
+ *     }
+ * }
+ */
+public abstract class X509NameEntryConverter
+{
+    /**
+     * Convert an inline encoded hex string rendition of an ASN.1
+     * object back into its corresponding ASN.1 object.
+     * 
+     * @param str the hex encoded object
+     * @param off the index at which the encoding starts
+     * @return the decoded object
+     */
+    protected DERObject convertHexEncoded(
+        String  str,
+        int     off)
+        throws IOException
+    {
+        str = Strings.toLowerCase(str);
+        byte[] data = new byte[(str.length() - off) / 2];
+        for (int index = 0; index != data.length; index++)
+        {
+            char left = str.charAt((index * 2) + off);
+            char right = str.charAt((index * 2) + off + 1);
+            
+            if (left < 'a')
+            {
+                data[index] = (byte)((left - '0') << 4);
+            }
+            else
+            {
+                data[index] = (byte)((left - 'a' + 10) << 4);
+            }
+            if (right < 'a')
+            {
+                data[index] |= (byte)(right - '0');
+            }
+            else
+            {
+                data[index] |= (byte)(right - 'a' + 10);
+            }
+        }
+
+        ASN1InputStream aIn = new ASN1InputStream(data);
+                                            
+        return aIn.readObject();
+    }
+    
+    /**
+     * return true if the passed in String can be represented without
+     * loss as a PrintableString, false otherwise.
+     */
+    protected boolean canBePrintable(
+        String  str)
+    {
+        return DERPrintableString.isPrintableString(str);
+    }
+    
+    /**
+     * Convert the passed in String value into the appropriate ASN.1
+     * encoded object.
+     * 
+     * @param oid the oid associated with the value in the DN.
+     * @param value the value of the particular DN component.
+     * @return the ASN.1 equivalent for the value.
+     */
+    public abstract DERObject getConvertedValue(DERObjectIdentifier oid, String value);
+}
diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/X509NameTokenizer.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509NameTokenizer.java
new file mode 100644
index 000000000..a0ccc78f0
--- /dev/null
+++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509NameTokenizer.java
@@ -0,0 +1,99 @@
+package com.google.bitcoin.bouncycastle.asn1.x509;
+
+/**
+ * class for breaking up an X500 Name into it's component tokens, ala
+ * java.util.StringTokenizer. We need this class as some of the
+ * lightweight Java environment don't support classes like
+ * StringTokenizer.
+ */
+public class X509NameTokenizer
+{
+    private String          value;
+    private int             index;
+    private char            seperator;
+    private StringBuffer    buf = new StringBuffer();
+
+    public X509NameTokenizer(
+        String  oid)
+    {
+        this(oid, ',');
+    }
+    
+    public X509NameTokenizer(
+        String  oid,
+        char    seperator)
+    {
+        this.value = oid;
+        this.index = -1;
+        this.seperator = seperator;
+    }
+
+    public boolean hasMoreTokens()
+    {
+        return (index != value.length());
+    }
+
+    public String nextToken()
+    {
+        if (index == value.length())
+        {
+            return null;
+        }
+
+        int     end = index + 1;
+        boolean quoted = false;
+        boolean escaped = false;
+
+        buf.setLength(0);
+
+        while (end != value.length())
+        {
+            char    c = value.charAt(end);
+
+            if (c == '"')
+            {
+                if (!escaped)
+                {
+                    quoted = !quoted;
+                }
+                else
+                {
+                    buf.append(c);
+                }
+                escaped = false;
+            }
+            else
+            {
+                if (escaped || quoted)
+                {
+                    if (c == '#' && buf.charAt(buf.length() - 1) == '=')
+                    {
+                        buf.append('\\');
+                    }
+                    else if (c == '+' && seperator != '+')
+                    {
+                        buf.append('\\');
+                    }
+                    buf.append(c);
+                    escaped = false;
+                }
+                else if (c == '\\')
+                {
+                    escaped = true;
+                }
+                else if (c == seperator)
+                {
+                    break;
+                }
+                else
+                {
+                    buf.append(c);
+                }
+            }
+            end++;
+        }
+
+        index = end;
+        return buf.toString().trim();
+    }
+}
diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/X509ObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509ObjectIdentifiers.java
new file mode 100644
index 000000000..811a2afc5
--- /dev/null
+++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/X509ObjectIdentifiers.java
@@ -0,0 +1,62 @@
+package com.google.bitcoin.bouncycastle.asn1.x509;
+
+import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier;
+
+public interface X509ObjectIdentifiers
+{
+    //
+    // base id
+    //
+    static final String                 id                      = "2.5.4";
+
+    static final DERObjectIdentifier    commonName              = new DERObjectIdentifier(id + ".3");
+    static final DERObjectIdentifier    countryName             = new DERObjectIdentifier(id + ".6");
+    static final DERObjectIdentifier    localityName            = new DERObjectIdentifier(id + ".7");
+    static final DERObjectIdentifier    stateOrProvinceName     = new DERObjectIdentifier(id + ".8");
+    static final DERObjectIdentifier    organization            = new DERObjectIdentifier(id + ".10");
+    static final DERObjectIdentifier    organizationalUnitName  = new DERObjectIdentifier(id + ".11");
+
+    static final DERObjectIdentifier    id_at_telephoneNumber   = new DERObjectIdentifier("2.5.4.20");
+    static final DERObjectIdentifier    id_at_name              = new DERObjectIdentifier(id + ".41");
+
+    // id-SHA1 OBJECT IDENTIFIER ::=    
+    //   {iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) 26 }    //
+    static final DERObjectIdentifier    id_SHA1                 = new DERObjectIdentifier("1.3.14.3.2.26");
+
+    //
+    // ripemd160 OBJECT IDENTIFIER ::=
+    //      {iso(1) identified-organization(3) TeleTrust(36) algorithm(3) hashAlgorithm(2) RIPEMD-160(1)}
+    //
+    static final DERObjectIdentifier    ripemd160               = new DERObjectIdentifier("1.3.36.3.2.1");
+
+    //
+    // ripemd160WithRSAEncryption OBJECT IDENTIFIER ::=
+    //      {iso(1) identified-organization(3) TeleTrust(36) algorithm(3) signatureAlgorithm(3) rsaSignature(1) rsaSignatureWithripemd160(2) }
+    //
+    static final DERObjectIdentifier    ripemd160WithRSAEncryption = new DERObjectIdentifier("1.3.36.3.3.1.2");
+
+
+    static final DERObjectIdentifier    id_ea_rsa = new DERObjectIdentifier("2.5.8.1.1");
+    
+    // id-pkix
+    static final DERObjectIdentifier id_pkix = new DERObjectIdentifier("1.3.6.1.5.5.7");
+
+    //
+    // private internet extensions
+    //
+    static final DERObjectIdentifier  id_pe = new DERObjectIdentifier(id_pkix + ".1");
+
+    //
+    // authority information access
+    //
+    static final DERObjectIdentifier  id_ad = new DERObjectIdentifier(id_pkix + ".48");
+    static final DERObjectIdentifier  id_ad_caIssuers = new DERObjectIdentifier(id_ad + ".2");
+    static final DERObjectIdentifier  id_ad_ocsp = new DERObjectIdentifier(id_ad + ".1");
+
+    //
+    //    OID for ocsp and crl uri in AuthorityInformationAccess extension
+    //
+    static final DERObjectIdentifier ocspAccessMethod = id_ad_ocsp;
+    static final DERObjectIdentifier crlAccessMethod = id_ad_caIssuers;
+}
+
diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/BiometricData.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/BiometricData.java
new file mode 100644
index 000000000..fda35f3b8
--- /dev/null
+++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/BiometricData.java
@@ -0,0 +1,124 @@
+package com.google.bitcoin.bouncycastle.asn1.x509.qualified;
+
+import java.util.Enumeration;
+
+import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable;
+import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector;
+import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString;
+import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence;
+import com.google.bitcoin.bouncycastle.asn1.DERIA5String;
+import com.google.bitcoin.bouncycastle.asn1.DERObject;
+import com.google.bitcoin.bouncycastle.asn1.DERSequence;
+import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * The BiometricData object.
+ * 
+ * BiometricData  ::=  SEQUENCE {
+ *       typeOfBiometricData  TypeOfBiometricData,
+ *       hashAlgorithm        AlgorithmIdentifier,
+ *       biometricDataHash    OCTET STRING,
+ *       sourceDataUri        IA5String OPTIONAL  }
+ * 
+ */ +public class BiometricData + extends ASN1Encodable +{ + TypeOfBiometricData typeOfBiometricData; + AlgorithmIdentifier hashAlgorithm; + ASN1OctetString biometricDataHash; + DERIA5String sourceDataUri; + + public static BiometricData getInstance( + Object obj) + { + if (obj == null || obj instanceof BiometricData) + { + return (BiometricData)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new BiometricData(ASN1Sequence.getInstance(obj)); + } + else + { + throw new IllegalArgumentException("unknown object in getInstance"); + } + } + + public BiometricData(ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + // typeOfBiometricData + typeOfBiometricData = TypeOfBiometricData.getInstance(e.nextElement()); + // hashAlgorithm + hashAlgorithm = AlgorithmIdentifier.getInstance(e.nextElement()); + // biometricDataHash + biometricDataHash = ASN1OctetString.getInstance(e.nextElement()); + // sourceDataUri + if (e.hasMoreElements()) + { + sourceDataUri = DERIA5String.getInstance(e.nextElement()); + } + } + + public BiometricData( + TypeOfBiometricData typeOfBiometricData, + AlgorithmIdentifier hashAlgorithm, + ASN1OctetString biometricDataHash, + DERIA5String sourceDataUri) + { + this.typeOfBiometricData = typeOfBiometricData; + this.hashAlgorithm = hashAlgorithm; + this.biometricDataHash = biometricDataHash; + this.sourceDataUri = sourceDataUri; + } + + public BiometricData( + TypeOfBiometricData typeOfBiometricData, + AlgorithmIdentifier hashAlgorithm, + ASN1OctetString biometricDataHash) + { + this.typeOfBiometricData = typeOfBiometricData; + this.hashAlgorithm = hashAlgorithm; + this.biometricDataHash = biometricDataHash; + this.sourceDataUri = null; + } + + public TypeOfBiometricData getTypeOfBiometricData() + { + return typeOfBiometricData; + } + + public AlgorithmIdentifier getHashAlgorithm() + { + return hashAlgorithm; + } + + public ASN1OctetString getBiometricDataHash() + { + return biometricDataHash; + } + + public DERIA5String getSourceDataUri() + { + return sourceDataUri; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector seq = new ASN1EncodableVector(); + seq.add(typeOfBiometricData); + seq.add(hashAlgorithm); + seq.add(biometricDataHash); + + if (sourceDataUri != null) + { + seq.add(sourceDataUri); + } + + return new DERSequence(seq); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/ETSIQCObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/ETSIQCObjectIdentifiers.java new file mode 100644 index 000000000..941eff345 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/ETSIQCObjectIdentifiers.java @@ -0,0 +1,16 @@ +package com.google.bitcoin.bouncycastle.asn1.x509.qualified; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface ETSIQCObjectIdentifiers +{ + // + // base id + // + static final String id_etsi_qcs = "0.4.0.1862.1"; + + static final DERObjectIdentifier id_etsi_qcs_QcCompliance = new DERObjectIdentifier(id_etsi_qcs+".1"); + static final DERObjectIdentifier id_etsi_qcs_LimiteValue = new DERObjectIdentifier(id_etsi_qcs+".2"); + static final DERObjectIdentifier id_etsi_qcs_RetentionPeriod = new DERObjectIdentifier(id_etsi_qcs+".3"); + static final DERObjectIdentifier id_etsi_qcs_QcSSCD = new DERObjectIdentifier(id_etsi_qcs+".4"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/Iso4217CurrencyCode.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/Iso4217CurrencyCode.java new file mode 100644 index 000000000..10574054f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/Iso4217CurrencyCode.java @@ -0,0 +1,93 @@ +package com.google.bitcoin.bouncycastle.asn1.x509.qualified; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERPrintableString; + +/** + * The Iso4217CurrencyCode object. + *
+ * Iso4217CurrencyCode  ::=  CHOICE {
+ *       alphabetic              PrintableString (SIZE 3), --Recommended
+ *       numeric              INTEGER (1..999) }
+ * -- Alphabetic or numeric currency code as defined in ISO 4217
+ * -- It is recommended that the Alphabetic form is used
+ * 
+ */ +public class Iso4217CurrencyCode + extends ASN1Encodable + implements ASN1Choice +{ + final int ALPHABETIC_MAXSIZE = 3; + final int NUMERIC_MINSIZE = 1; + final int NUMERIC_MAXSIZE = 999; + + DEREncodable obj; + int numeric; + + public static Iso4217CurrencyCode getInstance( + Object obj) + { + if (obj == null || obj instanceof Iso4217CurrencyCode) + { + return (Iso4217CurrencyCode)obj; + } + + if (obj instanceof DERInteger) + { + DERInteger numericobj = DERInteger.getInstance(obj); + int numeric = numericobj.getValue().intValue(); + return new Iso4217CurrencyCode(numeric); + } + else + if (obj instanceof DERPrintableString) + { + DERPrintableString alphabetic = DERPrintableString.getInstance(obj); + return new Iso4217CurrencyCode(alphabetic.getString()); + } + throw new IllegalArgumentException("unknown object in getInstance"); + } + + public Iso4217CurrencyCode( + int numeric) + { + if (numeric > NUMERIC_MAXSIZE || numeric < NUMERIC_MINSIZE) + { + throw new IllegalArgumentException("wrong size in numeric code : not in (" +NUMERIC_MINSIZE +".."+ NUMERIC_MAXSIZE +")"); + } + obj = new DERInteger(numeric); + } + + public Iso4217CurrencyCode( + String alphabetic) + { + if (alphabetic.length() > ALPHABETIC_MAXSIZE) + { + throw new IllegalArgumentException("wrong size in alphabetic code : max size is " + ALPHABETIC_MAXSIZE); + } + obj = new DERPrintableString(alphabetic); + } + + public boolean isAlphabetic() + { + return obj instanceof DERPrintableString; + } + + public String getAlphabetic() + { + return ((DERPrintableString)obj).getString(); + } + + public int getNumeric() + { + return ((DERInteger)obj).getValue().intValue(); + } + + public DERObject toASN1Object() + { + return obj.getDERObject(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/MonetaryValue.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/MonetaryValue.java new file mode 100644 index 000000000..9a3c1e3fb --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/MonetaryValue.java @@ -0,0 +1,92 @@ +package com.google.bitcoin.bouncycastle.asn1.x509.qualified; + +import java.math.BigInteger; +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * The MonetaryValue object. + *
+ * MonetaryValue  ::=  SEQUENCE {
+ *       currency              Iso4217CurrencyCode,
+ *       amount               INTEGER, 
+ *       exponent             INTEGER }
+ * -- value = amount * 10^exponent
+ * 
+ */ +public class MonetaryValue + extends ASN1Encodable +{ + Iso4217CurrencyCode currency; + DERInteger amount; + DERInteger exponent; + + public static MonetaryValue getInstance( + Object obj) + { + if (obj == null || obj instanceof MonetaryValue) + { + return (MonetaryValue)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new MonetaryValue(ASN1Sequence.getInstance(obj)); + } + + throw new IllegalArgumentException("unknown object in getInstance"); + } + + public MonetaryValue( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + // currency + currency = Iso4217CurrencyCode.getInstance(e.nextElement()); + // hashAlgorithm + amount = DERInteger.getInstance(e.nextElement()); + // exponent + exponent = DERInteger.getInstance(e.nextElement()); + } + + public MonetaryValue( + Iso4217CurrencyCode currency, + int amount, + int exponent) + { + this.currency = currency; + this.amount = new DERInteger(amount); + this.exponent = new DERInteger(exponent); + } + + public Iso4217CurrencyCode getCurrency() + { + return currency; + } + + public BigInteger getAmount() + { + return amount.getValue(); + } + + public BigInteger getExponent() + { + return exponent.getValue(); + } + + public DERObject toASN1Object() + { + ASN1EncodableVector seq = new ASN1EncodableVector(); + seq.add(currency); + seq.add(amount); + seq.add(exponent); + + return new DERSequence(seq); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/QCStatement.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/QCStatement.java new file mode 100644 index 000000000..a4f4b99eb --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/QCStatement.java @@ -0,0 +1,95 @@ +package com.google.bitcoin.bouncycastle.asn1.x509.qualified; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * The QCStatement object. + *
+ * QCStatement ::= SEQUENCE {
+ *   statementId        OBJECT IDENTIFIER,
+ *   statementInfo      ANY DEFINED BY statementId OPTIONAL} 
+ * 
+ */ + +public class QCStatement + extends ASN1Encodable + implements ETSIQCObjectIdentifiers, RFC3739QCObjectIdentifiers +{ + DERObjectIdentifier qcStatementId; + ASN1Encodable qcStatementInfo; + + public static QCStatement getInstance( + Object obj) + { + if (obj == null || obj instanceof QCStatement) + { + return (QCStatement)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new QCStatement(ASN1Sequence.getInstance(obj)); + } + + throw new IllegalArgumentException("unknown object in getInstance"); + } + + public QCStatement( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + // qcStatementId + qcStatementId = DERObjectIdentifier.getInstance(e.nextElement()); + // qcstatementInfo + if (e.hasMoreElements()) + { + qcStatementInfo = (ASN1Encodable) e.nextElement(); + } + } + + public QCStatement( + DERObjectIdentifier qcStatementId) + { + this.qcStatementId = qcStatementId; + this.qcStatementInfo = null; + } + + public QCStatement( + DERObjectIdentifier qcStatementId, + ASN1Encodable qcStatementInfo) + { + this.qcStatementId = qcStatementId; + this.qcStatementInfo = qcStatementInfo; + } + + public DERObjectIdentifier getStatementId() + { + return qcStatementId; + } + + public ASN1Encodable getStatementInfo() + { + return qcStatementInfo; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector seq = new ASN1EncodableVector(); + seq.add(qcStatementId); + + if (qcStatementInfo != null) + { + seq.add(qcStatementInfo); + } + + return new DERSequence(seq); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/RFC3739QCObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/RFC3739QCObjectIdentifiers.java new file mode 100644 index 000000000..584d08a4d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/RFC3739QCObjectIdentifiers.java @@ -0,0 +1,14 @@ +package com.google.bitcoin.bouncycastle.asn1.x509.qualified; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface RFC3739QCObjectIdentifiers +{ + // + // base id + // + static final String id_qcs = "1.3.6.1.5.5.7.11"; + + static final DERObjectIdentifier id_qcs_pkixQCSyntax_v1 = new DERObjectIdentifier(id_qcs+".1"); + static final DERObjectIdentifier id_qcs_pkixQCSyntax_v2 = new DERObjectIdentifier(id_qcs+".2"); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/SemanticsInformation.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/SemanticsInformation.java new file mode 100644 index 000000000..d8c68231f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/SemanticsInformation.java @@ -0,0 +1,130 @@ +package com.google.bitcoin.bouncycastle.asn1.x509.qualified; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.x509.GeneralName; + +/** + * The SemanticsInformation object. + *
+ *       SemanticsInformation ::= SEQUENCE {
+ *         semanticsIdentifier        OBJECT IDENTIFIER   OPTIONAL,
+ *         nameRegistrationAuthorities NameRegistrationAuthorities
+ *                                                         OPTIONAL }
+ *         (WITH COMPONENTS {..., semanticsIdentifier PRESENT}|
+ *          WITH COMPONENTS {..., nameRegistrationAuthorities PRESENT})
+ *
+ *     NameRegistrationAuthorities ::=  SEQUENCE SIZE (1..MAX) OF
+ *         GeneralName
+ * 
+ */ +public class SemanticsInformation extends ASN1Encodable +{ + DERObjectIdentifier semanticsIdentifier; + GeneralName[] nameRegistrationAuthorities; + + public static SemanticsInformation getInstance(Object obj) + { + if (obj == null || obj instanceof SemanticsInformation) + { + return (SemanticsInformation)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new SemanticsInformation(ASN1Sequence.getInstance(obj)); + } + + throw new IllegalArgumentException("unknown object in getInstance"); + } + + public SemanticsInformation(ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + if (seq.size() < 1) + { + throw new IllegalArgumentException("no objects in SemanticsInformation"); + } + + Object object = e.nextElement(); + if (object instanceof DERObjectIdentifier) + { + semanticsIdentifier = DERObjectIdentifier.getInstance(object); + if (e.hasMoreElements()) + { + object = e.nextElement(); + } + else + { + object = null; + } + } + + if (object != null) + { + ASN1Sequence generalNameSeq = ASN1Sequence.getInstance(object); + nameRegistrationAuthorities = new GeneralName[generalNameSeq.size()]; + for (int i= 0; i < generalNameSeq.size(); i++) + { + nameRegistrationAuthorities[i] = GeneralName.getInstance(generalNameSeq.getObjectAt(i)); + } + } + } + + public SemanticsInformation( + DERObjectIdentifier semanticsIdentifier, + GeneralName[] generalNames) + { + this.semanticsIdentifier = semanticsIdentifier; + this.nameRegistrationAuthorities = generalNames; + } + + public SemanticsInformation(DERObjectIdentifier semanticsIdentifier) + { + this.semanticsIdentifier = semanticsIdentifier; + this.nameRegistrationAuthorities = null; + } + + public SemanticsInformation(GeneralName[] generalNames) + { + this.semanticsIdentifier = null; + this.nameRegistrationAuthorities = generalNames; + } + + public DERObjectIdentifier getSemanticsIdentifier() + { + return semanticsIdentifier; + } + + public GeneralName[] getNameRegistrationAuthorities() + { + return nameRegistrationAuthorities; + } + + public DERObject toASN1Object() + { + ASN1EncodableVector seq = new ASN1EncodableVector(); + + if (this.semanticsIdentifier != null) + { + seq.add(semanticsIdentifier); + } + if (this.nameRegistrationAuthorities != null) + { + ASN1EncodableVector seqname = new ASN1EncodableVector(); + for (int i = 0; i < nameRegistrationAuthorities.length; i++) + { + seqname.add(nameRegistrationAuthorities[i]); + } + seq.add(new DERSequence(seqname)); + } + + return new DERSequence(seq); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/TypeOfBiometricData.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/TypeOfBiometricData.java new file mode 100644 index 000000000..085aee8ef --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/qualified/TypeOfBiometricData.java @@ -0,0 +1,90 @@ +package com.google.bitcoin.bouncycastle.asn1.x509.qualified; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +/** + * The TypeOfBiometricData object. + *
+ * TypeOfBiometricData ::= CHOICE {
+ *   predefinedBiometricType   PredefinedBiometricType,
+ *   biometricDataOid          OBJECT IDENTIFIER }
+ *
+ * PredefinedBiometricType ::= INTEGER {
+ *   picture(0),handwritten-signature(1)}
+ *   (picture|handwritten-signature)
+ * 
+ */ +public class TypeOfBiometricData + extends ASN1Encodable + implements ASN1Choice +{ + public static final int PICTURE = 0; + public static final int HANDWRITTEN_SIGNATURE = 1; + + DEREncodable obj; + + public static TypeOfBiometricData getInstance(Object obj) + { + if (obj == null || obj instanceof TypeOfBiometricData) + { + return (TypeOfBiometricData)obj; + } + + if (obj instanceof DERInteger) + { + DERInteger predefinedBiometricTypeObj = DERInteger.getInstance(obj); + int predefinedBiometricType = predefinedBiometricTypeObj.getValue().intValue(); + + return new TypeOfBiometricData(predefinedBiometricType); + } + else if (obj instanceof DERObjectIdentifier) + { + DERObjectIdentifier BiometricDataID = DERObjectIdentifier.getInstance(obj); + return new TypeOfBiometricData(BiometricDataID); + } + + throw new IllegalArgumentException("unknown object in getInstance"); + } + + public TypeOfBiometricData(int predefinedBiometricType) + { + if (predefinedBiometricType == PICTURE || predefinedBiometricType == HANDWRITTEN_SIGNATURE) + { + obj = new DERInteger(predefinedBiometricType); + } + else + { + throw new IllegalArgumentException("unknow PredefinedBiometricType : " + predefinedBiometricType); + } + } + + public TypeOfBiometricData(DERObjectIdentifier BiometricDataID) + { + obj = BiometricDataID; + } + + public boolean isPredefined() + { + return obj instanceof DERInteger; + } + + public int getPredefinedBiometricType() + { + return ((DERInteger)obj).getValue().intValue(); + } + + public DERObjectIdentifier getBiometricDataOid() + { + return (DERObjectIdentifier)obj; + } + + public DERObject toASN1Object() + { + return obj.getDERObject(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/sigi/NameOrPseudonym.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/sigi/NameOrPseudonym.java new file mode 100644 index 000000000..c6f8a4bd5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/sigi/NameOrPseudonym.java @@ -0,0 +1,191 @@ +package com.google.bitcoin.bouncycastle.asn1.x509.sigi; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERString; +import com.google.bitcoin.bouncycastle.asn1.x500.DirectoryString; + +import java.util.Enumeration; + +/** + * Structure for a name or pseudonym. + * + *
+ *       NameOrPseudonym ::= CHOICE {
+ *            surAndGivenName SEQUENCE {
+ *              surName DirectoryString,
+ *              givenName SEQUENCE OF DirectoryString 
+ *         },
+ *            pseudonym DirectoryString 
+ *       }
+ * 
+ * + * @see com.google.bitcoin.bouncycastle.asn1.x509.sigi.PersonalData + * + */ +public class NameOrPseudonym + extends ASN1Encodable + implements ASN1Choice +{ + private DirectoryString pseudonym; + + private DirectoryString surname; + + private ASN1Sequence givenName; + + public static NameOrPseudonym getInstance(Object obj) + { + if (obj == null || obj instanceof NameOrPseudonym) + { + return (NameOrPseudonym)obj; + } + + if (obj instanceof DERString) + { + return new NameOrPseudonym(DirectoryString.getInstance(obj)); + } + + if (obj instanceof ASN1Sequence) + { + return new NameOrPseudonym((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + + obj.getClass().getName()); + } + + /** + * Constructor from DERString. + *

+ * The sequence is of type NameOrPseudonym: + *

+ *

+     *       NameOrPseudonym ::= CHOICE {
+     *            surAndGivenName SEQUENCE {
+     *              surName DirectoryString,
+     *              givenName SEQUENCE OF DirectoryString
+     *         },
+     *            pseudonym DirectoryString
+     *       }
+     * 
+ * @param pseudonym pseudonym value to use. + */ + public NameOrPseudonym(DirectoryString pseudonym) + { + this.pseudonym = pseudonym; + } + + /** + * Constructor from ASN1Sequence. + *

+ * The sequence is of type NameOrPseudonym: + *

+ *

+     *       NameOrPseudonym ::= CHOICE {
+     *            surAndGivenName SEQUENCE {
+     *              surName DirectoryString,
+     *              givenName SEQUENCE OF DirectoryString
+     *         },
+     *            pseudonym DirectoryString
+     *       }
+     * 
+ * + * @param seq The ASN.1 sequence. + */ + private NameOrPseudonym(ASN1Sequence seq) + { + if (seq.size() != 2) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + if (!(seq.getObjectAt(0) instanceof DERString)) + { + throw new IllegalArgumentException("Bad object encountered: " + + seq.getObjectAt(0).getClass()); + } + + surname = DirectoryString.getInstance(seq.getObjectAt(0)); + givenName = ASN1Sequence.getInstance(seq.getObjectAt(1)); + } + + /** + * Constructor from a given details. + * + * @param pseudonym The pseudonym. + */ + public NameOrPseudonym(String pseudonym) + { + this(new DirectoryString(pseudonym)); + } + + /** + * Constructor from a given details. + * + * @param surname The surname. + * @param givenName A sequence of directory strings making up the givenName + */ + public NameOrPseudonym(DirectoryString surname, ASN1Sequence givenName) + { + this.surname = surname; + this.givenName = givenName; + } + + public DirectoryString getPseudonym() + { + return pseudonym; + } + + public DirectoryString getSurname() + { + return surname; + } + + public DirectoryString[] getGivenName() + { + DirectoryString[] items = new DirectoryString[givenName.size()]; + int count = 0; + for (Enumeration e = givenName.getObjects(); e.hasMoreElements();) + { + items[count++] = DirectoryString.getInstance(e.nextElement()); + } + return items; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+ * Returns: + *

+ *

+     *       NameOrPseudonym ::= CHOICE {
+     *            surAndGivenName SEQUENCE {
+     *              surName DirectoryString,
+     *              givenName SEQUENCE OF DirectoryString
+     *         },
+     *            pseudonym DirectoryString
+     *       }
+     * 
+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + if (pseudonym != null) + { + return pseudonym.toASN1Object(); + } + else + { + ASN1EncodableVector vec1 = new ASN1EncodableVector(); + vec1.add(surname); + vec1.add(givenName); + return new DERSequence(vec1); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/sigi/PersonalData.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/sigi/PersonalData.java new file mode 100644 index 000000000..af4ea4919 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/sigi/PersonalData.java @@ -0,0 +1,214 @@ +package com.google.bitcoin.bouncycastle.asn1.x509.sigi; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERGeneralizedTime; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERPrintableString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x500.DirectoryString; + +import java.math.BigInteger; +import java.util.Enumeration; + +/** + * Contains personal data for the otherName field in the subjectAltNames + * extension. + *

+ *

+ *     PersonalData ::= SEQUENCE {
+ *       nameOrPseudonym NameOrPseudonym,
+ *       nameDistinguisher [0] INTEGER OPTIONAL,
+ *       dateOfBirth [1] GeneralizedTime OPTIONAL,
+ *       placeOfBirth [2] DirectoryString OPTIONAL,
+ *       gender [3] PrintableString OPTIONAL,
+ *       postalAddress [4] DirectoryString OPTIONAL
+ *       }
+ * 
+ * + * @see com.google.bitcoin.bouncycastle.asn1.x509.sigi.NameOrPseudonym + * @see com.google.bitcoin.bouncycastle.asn1.x509.sigi.SigIObjectIdentifiers + */ +public class PersonalData + extends ASN1Encodable +{ + private NameOrPseudonym nameOrPseudonym; + private BigInteger nameDistinguisher; + private DERGeneralizedTime dateOfBirth; + private DirectoryString placeOfBirth; + private String gender; + private DirectoryString postalAddress; + + public static PersonalData getInstance(Object obj) + { + if (obj == null || obj instanceof PersonalData) + { + return (PersonalData)obj; + } + + if (obj instanceof ASN1Sequence) + { + return new PersonalData((ASN1Sequence)obj); + } + + throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); + } + + /** + * Constructor from ASN1Sequence. + *

+ * The sequence is of type NameOrPseudonym: + *

+ *

+     *     PersonalData ::= SEQUENCE {
+     *       nameOrPseudonym NameOrPseudonym,
+     *       nameDistinguisher [0] INTEGER OPTIONAL,
+     *       dateOfBirth [1] GeneralizedTime OPTIONAL,
+     *       placeOfBirth [2] DirectoryString OPTIONAL,
+     *       gender [3] PrintableString OPTIONAL,
+     *       postalAddress [4] DirectoryString OPTIONAL
+     *       }
+     * 
+ * + * @param seq The ASN.1 sequence. + */ + private PersonalData(ASN1Sequence seq) + { + if (seq.size() < 1) + { + throw new IllegalArgumentException("Bad sequence size: " + + seq.size()); + } + + Enumeration e = seq.getObjects(); + + nameOrPseudonym = NameOrPseudonym.getInstance(e.nextElement()); + + while (e.hasMoreElements()) + { + ASN1TaggedObject o = ASN1TaggedObject.getInstance(e.nextElement()); + int tag = o.getTagNo(); + switch (tag) + { + case 0: + nameDistinguisher = DERInteger.getInstance(o, false).getValue(); + break; + case 1: + dateOfBirth = DERGeneralizedTime.getInstance(o, false); + break; + case 2: + placeOfBirth = DirectoryString.getInstance(o, true); + break; + case 3: + gender = DERPrintableString.getInstance(o, false).getString(); + break; + case 4: + postalAddress = DirectoryString.getInstance(o, true); + break; + default: + throw new IllegalArgumentException("Bad tag number: " + o.getTagNo()); + } + } + } + + /** + * Constructor from a given details. + * + * @param nameOrPseudonym Name or pseudonym. + * @param nameDistinguisher Name distinguisher. + * @param dateOfBirth Date of birth. + * @param placeOfBirth Place of birth. + * @param gender Gender. + * @param postalAddress Postal Address. + */ + public PersonalData(NameOrPseudonym nameOrPseudonym, + BigInteger nameDistinguisher, DERGeneralizedTime dateOfBirth, + DirectoryString placeOfBirth, String gender, DirectoryString postalAddress) + { + this.nameOrPseudonym = nameOrPseudonym; + this.dateOfBirth = dateOfBirth; + this.gender = gender; + this.nameDistinguisher = nameDistinguisher; + this.postalAddress = postalAddress; + this.placeOfBirth = placeOfBirth; + } + + public NameOrPseudonym getNameOrPseudonym() + { + return nameOrPseudonym; + } + + public BigInteger getNameDistinguisher() + { + return nameDistinguisher; + } + + public DERGeneralizedTime getDateOfBirth() + { + return dateOfBirth; + } + + public DirectoryString getPlaceOfBirth() + { + return placeOfBirth; + } + + public String getGender() + { + return gender; + } + + public DirectoryString getPostalAddress() + { + return postalAddress; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+ * Returns: + *

+ *

+     *     PersonalData ::= SEQUENCE {
+     *       nameOrPseudonym NameOrPseudonym,
+     *       nameDistinguisher [0] INTEGER OPTIONAL,
+     *       dateOfBirth [1] GeneralizedTime OPTIONAL,
+     *       placeOfBirth [2] DirectoryString OPTIONAL,
+     *       gender [3] PrintableString OPTIONAL,
+     *       postalAddress [4] DirectoryString OPTIONAL
+     *       }
+     * 
+ * + * @return a DERObject + */ + public DERObject toASN1Object() + { + ASN1EncodableVector vec = new ASN1EncodableVector(); + vec.add(nameOrPseudonym); + if (nameDistinguisher != null) + { + vec.add(new DERTaggedObject(false, 0, new DERInteger(nameDistinguisher))); + } + if (dateOfBirth != null) + { + vec.add(new DERTaggedObject(false, 1, dateOfBirth)); + } + if (placeOfBirth != null) + { + vec.add(new DERTaggedObject(true, 2, placeOfBirth)); + } + if (gender != null) + { + vec.add(new DERTaggedObject(false, 3, new DERPrintableString(gender, true))); + } + if (postalAddress != null) + { + vec.add(new DERTaggedObject(true, 4, postalAddress)); + } + return new DERSequence(vec); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x509/sigi/SigIObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/x509/sigi/SigIObjectIdentifiers.java new file mode 100644 index 000000000..6584a40d7 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x509/sigi/SigIObjectIdentifiers.java @@ -0,0 +1,45 @@ +package com.google.bitcoin.bouncycastle.asn1.x509.sigi; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +/** + * Object Identifiers of SigI specifciation (German Signature Law + * Interoperability specification). + */ +public interface SigIObjectIdentifiers +{ + public final static DERObjectIdentifier id_sigi = new DERObjectIdentifier("1.3.36.8"); + + /** + * Key purpose IDs for German SigI (Signature Interoperability + * Specification) + */ + public final static DERObjectIdentifier id_sigi_kp = new DERObjectIdentifier(id_sigi + ".2"); + + /** + * Certificate policy IDs for German SigI (Signature Interoperability + * Specification) + */ + public final static DERObjectIdentifier id_sigi_cp = new DERObjectIdentifier(id_sigi + ".1"); + + /** + * Other Name IDs for German SigI (Signature Interoperability Specification) + */ + public final static DERObjectIdentifier id_sigi_on = new DERObjectIdentifier(id_sigi + ".4"); + + /** + * To be used for for the generation of directory service certificates. + */ + public static final DERObjectIdentifier id_sigi_kp_directoryService = new DERObjectIdentifier(id_sigi_kp + ".1"); + + /** + * ID for PersonalData + */ + public static final DERObjectIdentifier id_sigi_on_personalData = new DERObjectIdentifier(id_sigi_on + ".1"); + + /** + * Certificate is conform to german signature law. + */ + public static final DERObjectIdentifier id_sigi_cp_sigconform = new DERObjectIdentifier(id_sigi_cp + ".1"); + +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x9/KeySpecificInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/x9/KeySpecificInfo.java new file mode 100644 index 000000000..63f16010a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x9/KeySpecificInfo.java @@ -0,0 +1,68 @@ +package com.google.bitcoin.bouncycastle.asn1.x9; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * ASN.1 def for Diffie-Hellman key exchange KeySpecificInfo structure. See + * RFC 2631, or X9.42, for further details. + */ +public class KeySpecificInfo + extends ASN1Encodable +{ + private DERObjectIdentifier algorithm; + private ASN1OctetString counter; + + public KeySpecificInfo( + DERObjectIdentifier algorithm, + ASN1OctetString counter) + { + this.algorithm = algorithm; + this.counter = counter; + } + + public KeySpecificInfo( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + algorithm = (DERObjectIdentifier)e.nextElement(); + counter = (ASN1OctetString)e.nextElement(); + } + + public DERObjectIdentifier getAlgorithm() + { + return algorithm; + } + + public ASN1OctetString getCounter() + { + return counter; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  KeySpecificInfo ::= SEQUENCE {
+     *      algorithm OBJECT IDENTIFIER,
+     *      counter OCTET STRING SIZE (4..4)
+     *  }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(algorithm); + v.add(counter); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x9/OtherInfo.java b/src/com/google/bitcoin/bouncycastle/asn1/x9/OtherInfo.java new file mode 100644 index 000000000..7e985ae10 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x9/OtherInfo.java @@ -0,0 +1,96 @@ +package com.google.bitcoin.bouncycastle.asn1.x9; + +import java.util.Enumeration; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; + +/** + * ANS.1 def for Diffie-Hellman key exchange OtherInfo structure. See + * RFC 2631, or X9.42, for further details. + */ +public class OtherInfo + extends ASN1Encodable +{ + private KeySpecificInfo keyInfo; + private ASN1OctetString partyAInfo; + private ASN1OctetString suppPubInfo; + + public OtherInfo( + KeySpecificInfo keyInfo, + ASN1OctetString partyAInfo, + ASN1OctetString suppPubInfo) + { + this.keyInfo = keyInfo; + this.partyAInfo = partyAInfo; + this.suppPubInfo = suppPubInfo; + } + + public OtherInfo( + ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + + keyInfo = new KeySpecificInfo((ASN1Sequence)e.nextElement()); + + while (e.hasMoreElements()) + { + DERTaggedObject o = (DERTaggedObject)e.nextElement(); + + if (o.getTagNo() == 0) + { + partyAInfo = (ASN1OctetString)o.getObject(); + } + else if (o.getTagNo() == 2) + { + suppPubInfo = (ASN1OctetString)o.getObject(); + } + } + } + + public KeySpecificInfo getKeyInfo() + { + return keyInfo; + } + + public ASN1OctetString getPartyAInfo() + { + return partyAInfo; + } + + public ASN1OctetString getSuppPubInfo() + { + return suppPubInfo; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  OtherInfo ::= SEQUENCE {
+     *      keyInfo KeySpecificInfo,
+     *      partyAInfo [0] OCTET STRING OPTIONAL,
+     *      suppPubInfo [2] OCTET STRING
+     *  }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(keyInfo); + + if (partyAInfo != null) + { + v.add(new DERTaggedObject(0, partyAInfo)); + } + + v.add(new DERTaggedObject(2, suppPubInfo)); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x9/X962NamedCurves.java b/src/com/google/bitcoin/bouncycastle/asn1/x9/X962NamedCurves.java new file mode 100644 index 000000000..f2ab8f3f6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x9/X962NamedCurves.java @@ -0,0 +1,621 @@ +package com.google.bitcoin.bouncycastle.asn1.x9; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.math.ec.ECCurve; +import com.google.bitcoin.bouncycastle.util.Strings; +import com.google.bitcoin.bouncycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.Enumeration; +import java.util.Hashtable; + + +/** + * table of the current named curves defined in X.962 EC-DSA. + */ +public class X962NamedCurves +{ + static X9ECParametersHolder prime192v1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve cFp192v1 = new ECCurve.Fp( + new BigInteger("6277101735386680763835789423207666416083908700390324961279"), + new BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffc", 16), + new BigInteger("64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", 16)); + + return new X9ECParameters( + cFp192v1, + cFp192v1.decodePoint( + Hex.decode("03188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012")), + new BigInteger("ffffffffffffffffffffffff99def836146bc9b1b4d22831", 16), + BigInteger.valueOf(1), + Hex.decode("3045AE6FC8422f64ED579528D38120EAE12196D5")); + } + }; + + static X9ECParametersHolder prime192v2 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve cFp192v2 = new ECCurve.Fp( + new BigInteger("6277101735386680763835789423207666416083908700390324961279"), + new BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffc", 16), + new BigInteger("cc22d6dfb95c6b25e49c0d6364a4e5980c393aa21668d953", 16)); + + return new X9ECParameters( + cFp192v2, + cFp192v2.decodePoint( + Hex.decode("03eea2bae7e1497842f2de7769cfe9c989c072ad696f48034a")), + new BigInteger("fffffffffffffffffffffffe5fb1a724dc80418648d8dd31", 16), + BigInteger.valueOf(1), + Hex.decode("31a92ee2029fd10d901b113e990710f0d21ac6b6")); + } + }; + + static X9ECParametersHolder prime192v3 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve cFp192v3 = new ECCurve.Fp( + new BigInteger("6277101735386680763835789423207666416083908700390324961279"), + new BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffc", 16), + new BigInteger("22123dc2395a05caa7423daeccc94760a7d462256bd56916", 16)); + + return new X9ECParameters( + cFp192v3, + cFp192v3.decodePoint( + Hex.decode("027d29778100c65a1da1783716588dce2b8b4aee8e228f1896")), + new BigInteger("ffffffffffffffffffffffff7a62d031c83f4294f640ec13", 16), + BigInteger.valueOf(1), + Hex.decode("c469684435deb378c4b65ca9591e2a5763059a2e")); + } + }; + + static X9ECParametersHolder prime239v1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve cFp239v1 = new ECCurve.Fp( + new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839"), + new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), + new BigInteger("6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a", 16)); + + return new X9ECParameters( + cFp239v1, + cFp239v1.decodePoint( + Hex.decode("020ffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf")), + new BigInteger("7fffffffffffffffffffffff7fffff9e5e9a9f5d9071fbd1522688909d0b", 16), + BigInteger.valueOf(1), + Hex.decode("e43bb460f0b80cc0c0b075798e948060f8321b7d")); + } + }; + + static X9ECParametersHolder prime239v2 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve cFp239v2 = new ECCurve.Fp( + new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839"), + new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), + new BigInteger("617fab6832576cbbfed50d99f0249c3fee58b94ba0038c7ae84c8c832f2c", 16)); + + return new X9ECParameters( + cFp239v2, + cFp239v2.decodePoint( + Hex.decode("0238af09d98727705120c921bb5e9e26296a3cdcf2f35757a0eafd87b830e7")), + new BigInteger("7fffffffffffffffffffffff800000cfa7e8594377d414c03821bc582063", 16), + BigInteger.valueOf(1), + Hex.decode("e8b4011604095303ca3b8099982be09fcb9ae616")); + } + }; + + static X9ECParametersHolder prime239v3 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve cFp239v3 = new ECCurve.Fp( + new BigInteger("883423532389192164791648750360308885314476597252960362792450860609699839"), + new BigInteger("7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc", 16), + new BigInteger("255705fa2a306654b1f4cb03d6a750a30c250102d4988717d9ba15ab6d3e", 16)); + + return new X9ECParameters( + cFp239v3, + cFp239v3.decodePoint( + Hex.decode("036768ae8e18bb92cfcf005c949aa2c6d94853d0e660bbf854b1c9505fe95a")), + new BigInteger("7fffffffffffffffffffffff7fffff975deb41b3a6057c3c432146526551", 16), + BigInteger.valueOf(1), + Hex.decode("7d7374168ffe3471b60a857686a19475d3bfa2ff")); + } + }; + + static X9ECParametersHolder prime256v1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + ECCurve cFp256v1 = new ECCurve.Fp( + new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853951"), + new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16), + new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16)); + + return new X9ECParameters( + cFp256v1, + cFp256v1.decodePoint( + Hex.decode("036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296")), + new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16), + BigInteger.valueOf(1), + Hex.decode("c49d360886e704936a6678e1139d26b7819f7e90")); + } + }; + + /* + * F2m Curves + */ + static X9ECParametersHolder c2pnb163v1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m163v1n = new BigInteger("0400000000000000000001E60FC8821CC74DAEAFC1", 16); + BigInteger c2m163v1h = BigInteger.valueOf(2); + + ECCurve c2m163v1 = new ECCurve.F2m( + 163, + 1, 2, 8, + new BigInteger("072546B5435234A422E0789675F432C89435DE5242", 16), + new BigInteger("00C9517D06D5240D3CFF38C74B20B6CD4D6F9DD4D9", 16), + c2m163v1n, c2m163v1h); + + return new X9ECParameters( + c2m163v1, + c2m163v1.decodePoint( + Hex.decode("0307AF69989546103D79329FCC3D74880F33BBE803CB")), + c2m163v1n, c2m163v1h, + Hex.decode("D2COFB15760860DEF1EEF4D696E6768756151754")); + } + }; + + static X9ECParametersHolder c2pnb163v2 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m163v2n = new BigInteger("03FFFFFFFFFFFFFFFFFFFDF64DE1151ADBB78F10A7", 16); + BigInteger c2m163v2h = BigInteger.valueOf(2); + + ECCurve c2m163v2 = new ECCurve.F2m( + 163, + 1, 2, 8, + new BigInteger("0108B39E77C4B108BED981ED0E890E117C511CF072", 16), + new BigInteger("0667ACEB38AF4E488C407433FFAE4F1C811638DF20", 16), + c2m163v2n, c2m163v2h); + + return new X9ECParameters( + c2m163v2, + c2m163v2.decodePoint( + Hex.decode("030024266E4EB5106D0A964D92C4860E2671DB9B6CC5")), + c2m163v2n, c2m163v2h, + null); + } + }; + + static X9ECParametersHolder c2pnb163v3 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m163v3n = new BigInteger("03FFFFFFFFFFFFFFFFFFFE1AEE140F110AFF961309", 16); + BigInteger c2m163v3h = BigInteger.valueOf(2); + + ECCurve c2m163v3 = new ECCurve.F2m( + 163, + 1, 2, 8, + new BigInteger("07A526C63D3E25A256A007699F5447E32AE456B50E", 16), + new BigInteger("03F7061798EB99E238FD6F1BF95B48FEEB4854252B", 16), + c2m163v3n, c2m163v3h); + + return new X9ECParameters( + c2m163v3, + c2m163v3.decodePoint( + Hex.decode("0202F9F87B7C574D0BDECF8A22E6524775F98CDEBDCB")), + c2m163v3n, c2m163v3h, + null); + } + }; + + static X9ECParametersHolder c2pnb176w1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m176w1n = new BigInteger("010092537397ECA4F6145799D62B0A19CE06FE26AD", 16); + BigInteger c2m176w1h = BigInteger.valueOf(0xFF6E); + + ECCurve c2m176w1 = new ECCurve.F2m( + 176, + 1, 2, 43, + new BigInteger("00E4E6DB2995065C407D9D39B8D0967B96704BA8E9C90B", 16), + new BigInteger("005DDA470ABE6414DE8EC133AE28E9BBD7FCEC0AE0FFF2", 16), + c2m176w1n, c2m176w1h); + + return new X9ECParameters( + c2m176w1, + c2m176w1.decodePoint( + Hex.decode("038D16C2866798B600F9F08BB4A8E860F3298CE04A5798")), + c2m176w1n, c2m176w1h, + null); + } + }; + + static X9ECParametersHolder c2tnb191v1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m191v1n = new BigInteger("40000000000000000000000004A20E90C39067C893BBB9A5", 16); + BigInteger c2m191v1h = BigInteger.valueOf(2); + + ECCurve c2m191v1 = new ECCurve.F2m( + 191, + 9, + new BigInteger("2866537B676752636A68F56554E12640276B649EF7526267", 16), + new BigInteger("2E45EF571F00786F67B0081B9495A3D95462F5DE0AA185EC", 16), + c2m191v1n, c2m191v1h); + + return new X9ECParameters( + c2m191v1, + c2m191v1.decodePoint( + Hex.decode("0236B3DAF8A23206F9C4F299D7B21A9C369137F2C84AE1AA0D")), + c2m191v1n, c2m191v1h, + Hex.decode("4E13CA542744D696E67687561517552F279A8C84")); + } + }; + + static X9ECParametersHolder c2tnb191v2 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m191v2n = new BigInteger("20000000000000000000000050508CB89F652824E06B8173", 16); + BigInteger c2m191v2h = BigInteger.valueOf(4); + + ECCurve c2m191v2 = new ECCurve.F2m( + 191, + 9, + new BigInteger("401028774D7777C7B7666D1366EA432071274F89FF01E718", 16), + new BigInteger("0620048D28BCBD03B6249C99182B7C8CD19700C362C46A01", 16), + c2m191v2n, c2m191v2h); + + return new X9ECParameters( + c2m191v2, + c2m191v2.decodePoint( + Hex.decode("023809B2B7CC1B28CC5A87926AAD83FD28789E81E2C9E3BF10")), + c2m191v2n, c2m191v2h, + null); + } + }; + + static X9ECParametersHolder c2tnb191v3 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m191v3n = new BigInteger("155555555555555555555555610C0B196812BFB6288A3EA3", 16); + BigInteger c2m191v3h = BigInteger.valueOf(6); + + ECCurve c2m191v3 = new ECCurve.F2m( + 191, + 9, + new BigInteger("6C01074756099122221056911C77D77E77A777E7E7E77FCB", 16), + new BigInteger("71FE1AF926CF847989EFEF8DB459F66394D90F32AD3F15E8", 16), + c2m191v3n, c2m191v3h); + + return new X9ECParameters( + c2m191v3, + c2m191v3.decodePoint( + Hex.decode("03375D4CE24FDE434489DE8746E71786015009E66E38A926DD")), + c2m191v3n, c2m191v3h, + null); + } + }; + + static X9ECParametersHolder c2pnb208w1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m208w1n = new BigInteger("0101BAF95C9723C57B6C21DA2EFF2D5ED588BDD5717E212F9D", 16); + BigInteger c2m208w1h = BigInteger.valueOf(0xFE48); + + ECCurve c2m208w1 = new ECCurve.F2m( + 208, + 1, 2, 83, + new BigInteger("0", 16), + new BigInteger("00C8619ED45A62E6212E1160349E2BFA844439FAFC2A3FD1638F9E", 16), + c2m208w1n, c2m208w1h); + + return new X9ECParameters( + c2m208w1, + c2m208w1.decodePoint( + Hex.decode("0289FDFBE4ABE193DF9559ECF07AC0CE78554E2784EB8C1ED1A57A")), + c2m208w1n, c2m208w1h, + null); + } + }; + + static X9ECParametersHolder c2tnb239v1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m239v1n = new BigInteger("2000000000000000000000000000000F4D42FFE1492A4993F1CAD666E447", 16); + BigInteger c2m239v1h = BigInteger.valueOf(4); + + ECCurve c2m239v1 = new ECCurve.F2m( + 239, + 36, + new BigInteger("32010857077C5431123A46B808906756F543423E8D27877578125778AC76", 16), + new BigInteger("790408F2EEDAF392B012EDEFB3392F30F4327C0CA3F31FC383C422AA8C16", 16), + c2m239v1n, c2m239v1h); + + return new X9ECParameters( + c2m239v1, + c2m239v1.decodePoint( + Hex.decode("0257927098FA932E7C0A96D3FD5B706EF7E5F5C156E16B7E7C86038552E91D")), + c2m239v1n, c2m239v1h, + null); + } + }; + + static X9ECParametersHolder c2tnb239v2 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m239v2n = new BigInteger("1555555555555555555555555555553C6F2885259C31E3FCDF154624522D", 16); + BigInteger c2m239v2h = BigInteger.valueOf(6); + + ECCurve c2m239v2 = new ECCurve.F2m( + 239, + 36, + new BigInteger("4230017757A767FAE42398569B746325D45313AF0766266479B75654E65F", 16), + new BigInteger("5037EA654196CFF0CD82B2C14A2FCF2E3FF8775285B545722F03EACDB74B", 16), + c2m239v2n, c2m239v2h); + + return new X9ECParameters( + c2m239v2, + c2m239v2.decodePoint( + Hex.decode("0228F9D04E900069C8DC47A08534FE76D2B900B7D7EF31F5709F200C4CA205")), + c2m239v2n, c2m239v2h, + null); + } + }; + + static X9ECParametersHolder c2tnb239v3 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m239v3n = new BigInteger("0CCCCCCCCCCCCCCCCCCCCCCCCCCCCCAC4912D2D9DF903EF9888B8A0E4CFF", 16); + BigInteger c2m239v3h = BigInteger.valueOf(10); + + ECCurve c2m239v3 = new ECCurve.F2m( + 239, + 36, + new BigInteger("01238774666A67766D6676F778E676B66999176666E687666D8766C66A9F", 16), + new BigInteger("6A941977BA9F6A435199ACFC51067ED587F519C5ECB541B8E44111DE1D40", 16), + c2m239v3n, c2m239v3h); + + return new X9ECParameters( + c2m239v3, + c2m239v3.decodePoint( + Hex.decode("0370F6E9D04D289C4E89913CE3530BFDE903977D42B146D539BF1BDE4E9C92")), + c2m239v3n, c2m239v3h, + null); + } + }; + + static X9ECParametersHolder c2pnb272w1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m272w1n = new BigInteger("0100FAF51354E0E39E4892DF6E319C72C8161603FA45AA7B998A167B8F1E629521", 16); + BigInteger c2m272w1h = BigInteger.valueOf(0xFF06); + + ECCurve c2m272w1 = new ECCurve.F2m( + 272, + 1, 3, 56, + new BigInteger("0091A091F03B5FBA4AB2CCF49C4EDD220FB028712D42BE752B2C40094DBACDB586FB20", 16), + new BigInteger("7167EFC92BB2E3CE7C8AAAFF34E12A9C557003D7C73A6FAF003F99F6CC8482E540F7", 16), + c2m272w1n, c2m272w1h); + + return new X9ECParameters( + c2m272w1, + c2m272w1.decodePoint( + Hex.decode("026108BABB2CEEBCF787058A056CBE0CFE622D7723A289E08A07AE13EF0D10D171DD8D")), + c2m272w1n, c2m272w1h, + null); + } + }; + + static X9ECParametersHolder c2pnb304w1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m304w1n = new BigInteger("0101D556572AABAC800101D556572AABAC8001022D5C91DD173F8FB561DA6899164443051D", 16); + BigInteger c2m304w1h = BigInteger.valueOf(0xFE2E); + + ECCurve c2m304w1 = new ECCurve.F2m( + 304, + 1, 2, 11, + new BigInteger("00FD0D693149A118F651E6DCE6802085377E5F882D1B510B44160074C1288078365A0396C8E681", 16), + new BigInteger("00BDDB97E555A50A908E43B01C798EA5DAA6788F1EA2794EFCF57166B8C14039601E55827340BE", 16), + c2m304w1n, c2m304w1h); + + return new X9ECParameters( + c2m304w1, + c2m304w1.decodePoint( + Hex.decode("02197B07845E9BE2D96ADB0F5F3C7F2CFFBD7A3EB8B6FEC35C7FD67F26DDF6285A644F740A2614")), + c2m304w1n, c2m304w1h, + null); + } + }; + + static X9ECParametersHolder c2tnb359v1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m359v1n = new BigInteger("01AF286BCA1AF286BCA1AF286BCA1AF286BCA1AF286BC9FB8F6B85C556892C20A7EB964FE7719E74F490758D3B", 16); + BigInteger c2m359v1h = BigInteger.valueOf(0x4C); + + ECCurve c2m359v1 = new ECCurve.F2m( + 359, + 68, + new BigInteger("5667676A654B20754F356EA92017D946567C46675556F19556A04616B567D223A5E05656FB549016A96656A557", 16), + new BigInteger("2472E2D0197C49363F1FE7F5B6DB075D52B6947D135D8CA445805D39BC345626089687742B6329E70680231988", 16), + c2m359v1n, c2m359v1h); + + return new X9ECParameters( + c2m359v1, + c2m359v1.decodePoint( + Hex.decode("033C258EF3047767E7EDE0F1FDAA79DAEE3841366A132E163ACED4ED2401DF9C6BDCDE98E8E707C07A2239B1B097")), + c2m359v1n, c2m359v1h, + null); + } + }; + + static X9ECParametersHolder c2pnb368w1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m368w1n = new BigInteger("010090512DA9AF72B08349D98A5DD4C7B0532ECA51CE03E2D10F3B7AC579BD87E909AE40A6F131E9CFCE5BD967", 16); + BigInteger c2m368w1h = BigInteger.valueOf(0xFF70); + + ECCurve c2m368w1 = new ECCurve.F2m( + 368, + 1, 2, 85, + new BigInteger("00E0D2EE25095206F5E2A4F9ED229F1F256E79A0E2B455970D8D0D865BD94778C576D62F0AB7519CCD2A1A906AE30D", 16), + new BigInteger("00FC1217D4320A90452C760A58EDCD30C8DD069B3C34453837A34ED50CB54917E1C2112D84D164F444F8F74786046A", 16), + c2m368w1n, c2m368w1h); + + return new X9ECParameters( + c2m368w1, + c2m368w1.decodePoint( + Hex.decode("021085E2755381DCCCE3C1557AFA10C2F0C0C2825646C5B34A394CBCFA8BC16B22E7E789E927BE216F02E1FB136A5F")), + c2m368w1n, c2m368w1h, + null); + } + }; + + static X9ECParametersHolder c2tnb431r1 = new X9ECParametersHolder() + { + protected X9ECParameters createParameters() + { + BigInteger c2m431r1n = new BigInteger("0340340340340340340340340340340340340340340340340340340323C313FAB50589703B5EC68D3587FEC60D161CC149C1AD4A91", 16); + BigInteger c2m431r1h = BigInteger.valueOf(0x2760); + + ECCurve c2m431r1 = new ECCurve.F2m( + 431, + 120, + new BigInteger("1A827EF00DD6FC0E234CAF046C6A5D8A85395B236CC4AD2CF32A0CADBDC9DDF620B0EB9906D0957F6C6FEACD615468DF104DE296CD8F", 16), + new BigInteger("10D9B4A3D9047D8B154359ABFB1B7F5485B04CEB868237DDC9DEDA982A679A5A919B626D4E50A8DD731B107A9962381FB5D807BF2618", 16), + c2m431r1n, c2m431r1h); + + return new X9ECParameters( + c2m431r1, + c2m431r1.decodePoint( + Hex.decode("02120FC05D3C67A99DE161D2F4092622FECA701BE4F50F4758714E8A87BBF2A658EF8C21E7C5EFE965361F6C2999C0C247B0DBD70CE6B7")), + c2m431r1n, c2m431r1h, + null); + } + }; + + static final Hashtable objIds = new Hashtable(); + static final Hashtable curves = new Hashtable(); + static final Hashtable names = new Hashtable(); + + static void defineCurve(String name, DERObjectIdentifier oid, X9ECParametersHolder holder) + { + objIds.put(name, oid); + names.put(oid, name); + curves.put(oid, holder); + } + + static + { + defineCurve("prime192v1", X9ObjectIdentifiers.prime192v1, prime192v1); + defineCurve("prime192v2", X9ObjectIdentifiers.prime192v2, prime192v2); + defineCurve("prime192v3", X9ObjectIdentifiers.prime192v3, prime192v3); + defineCurve("prime239v1", X9ObjectIdentifiers.prime239v1, prime239v1); + defineCurve("prime239v2", X9ObjectIdentifiers.prime239v2, prime239v2); + defineCurve("prime239v3", X9ObjectIdentifiers.prime239v3, prime239v3); + defineCurve("prime256v1", X9ObjectIdentifiers.prime256v1, prime256v1); + defineCurve("c2pnb163v1", X9ObjectIdentifiers.c2pnb163v1, c2pnb163v1); + defineCurve("c2pnb163v2", X9ObjectIdentifiers.c2pnb163v2, c2pnb163v2); + defineCurve("c2pnb163v3", X9ObjectIdentifiers.c2pnb163v3, c2pnb163v3); + defineCurve("c2pnb176w1", X9ObjectIdentifiers.c2pnb176w1, c2pnb176w1); + defineCurve("c2tnb191v1", X9ObjectIdentifiers.c2tnb191v1, c2tnb191v1); + defineCurve("c2tnb191v2", X9ObjectIdentifiers.c2tnb191v2, c2tnb191v2); + defineCurve("c2tnb191v3", X9ObjectIdentifiers.c2tnb191v3, c2tnb191v3); + defineCurve("c2pnb208w1", X9ObjectIdentifiers.c2pnb208w1, c2pnb208w1); + defineCurve("c2tnb239v1", X9ObjectIdentifiers.c2tnb239v1, c2tnb239v1); + defineCurve("c2tnb239v2", X9ObjectIdentifiers.c2tnb239v2, c2tnb239v2); + defineCurve("c2tnb239v3", X9ObjectIdentifiers.c2tnb239v3, c2tnb239v3); + defineCurve("c2pnb272w1", X9ObjectIdentifiers.c2pnb272w1, c2pnb272w1); + defineCurve("c2pnb304w1", X9ObjectIdentifiers.c2pnb304w1, c2pnb304w1); + defineCurve("c2tnb359v1", X9ObjectIdentifiers.c2tnb359v1, c2tnb359v1); + defineCurve("c2pnb368w1", X9ObjectIdentifiers.c2pnb368w1, c2pnb368w1); + defineCurve("c2tnb431r1", X9ObjectIdentifiers.c2tnb431r1, c2tnb431r1); + } + + public static X9ECParameters getByName( + String name) + { + DERObjectIdentifier oid = (DERObjectIdentifier)objIds.get(Strings.toLowerCase(name)); + + if (oid != null) + { + return getByOID(oid); + } + + return null; + } + + /** + * return the X9ECParameters object for the named curve represented by + * the passed in object identifier. Null if the curve isn't present. + * + * @param oid an object identifier representing a named curve, if present. + */ + public static X9ECParameters getByOID( + DERObjectIdentifier oid) + { + X9ECParametersHolder holder = (X9ECParametersHolder)curves.get(oid); + + if (holder != null) + { + return holder.getParameters(); + } + + return null; + } + + /** + * return the object identifier signified by the passed in name. Null + * if there is no object identifier associated with name. + * + * @return the object identifier associated with name, if present. + */ + public static DERObjectIdentifier getOID( + String name) + { + return (DERObjectIdentifier)objIds.get(Strings.toLowerCase(name)); + } + + /** + * return the named curve name represented by the given object identifier. + */ + public static String getName( + DERObjectIdentifier oid) + { + return (String)names.get(oid); + } + + /** + * returns an enumeration containing the name strings for curves + * contained in this structure. + */ + public static Enumeration getNames() + { + return objIds.keys(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x9/X962Parameters.java b/src/com/google/bitcoin/bouncycastle/asn1/x9/X962Parameters.java new file mode 100644 index 000000000..4327bbad6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x9/X962Parameters.java @@ -0,0 +1,86 @@ +package com.google.bitcoin.bouncycastle.asn1.x9; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Choice; +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1Null; +import com.google.bitcoin.bouncycastle.asn1.ASN1TaggedObject; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public class X962Parameters + extends ASN1Encodable + implements ASN1Choice +{ + private DERObject params = null; + + public static X962Parameters getInstance( + Object obj) + { + if (obj == null || obj instanceof X962Parameters) + { + return (X962Parameters)obj; + } + + if (obj instanceof DERObject) + { + return new X962Parameters((DERObject)obj); + } + + throw new IllegalArgumentException("unknown object in getInstance()"); + } + + public static X962Parameters getInstance( + ASN1TaggedObject obj, + boolean explicit) + { + return getInstance(obj.getObject()); // must be explicitly tagged + } + + public X962Parameters( + X9ECParameters ecParameters) + { + this.params = ecParameters.getDERObject(); + } + + public X962Parameters( + DERObjectIdentifier namedCurve) + { + this.params = namedCurve; + } + + public X962Parameters( + DERObject obj) + { + this.params = obj; + } + + public boolean isNamedCurve() + { + return (params instanceof DERObjectIdentifier); + } + + public boolean isImplicitlyCA() + { + return (params instanceof ASN1Null); + } + + public DERObject getParameters() + { + return params; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     * Parameters ::= CHOICE {
+     *    ecParameters ECParameters,
+     *    namedCurve   CURVES.&id({CurveNames}),
+     *    implicitlyCA NULL
+     * }
+     * 
+ */ + public DERObject toASN1Object() + { + return params; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x9/X9Curve.java b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9Curve.java new file mode 100644 index 000000000..13ad9df93 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9Curve.java @@ -0,0 +1,161 @@ +package com.google.bitcoin.bouncycastle.asn1.x9; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.math.ec.ECCurve; + +/** + * ASN.1 def for Elliptic-Curve Curve structure. See + * X9.62, for further details. + */ +public class X9Curve + extends ASN1Encodable + implements X9ObjectIdentifiers +{ + private ECCurve curve; + private byte[] seed; + private DERObjectIdentifier fieldIdentifier = null; + + public X9Curve( + ECCurve curve) + { + this.curve = curve; + this.seed = null; + setFieldIdentifier(); + } + + public X9Curve( + ECCurve curve, + byte[] seed) + { + this.curve = curve; + this.seed = seed; + setFieldIdentifier(); + } + + public X9Curve( + X9FieldID fieldID, + ASN1Sequence seq) + { + fieldIdentifier = fieldID.getIdentifier(); + if (fieldIdentifier.equals(prime_field)) + { + BigInteger p = ((DERInteger)fieldID.getParameters()).getValue(); + X9FieldElement x9A = new X9FieldElement(p, (ASN1OctetString)seq.getObjectAt(0)); + X9FieldElement x9B = new X9FieldElement(p, (ASN1OctetString)seq.getObjectAt(1)); + curve = new ECCurve.Fp(p, x9A.getValue().toBigInteger(), x9B.getValue().toBigInteger()); + } + else + { + if (fieldIdentifier.equals(characteristic_two_field)) + { + // Characteristic two field + DERSequence parameters = (DERSequence)fieldID.getParameters(); + int m = ((DERInteger)parameters.getObjectAt(0)).getValue(). + intValue(); + DERObjectIdentifier representation + = (DERObjectIdentifier)parameters.getObjectAt(1); + + int k1 = 0; + int k2 = 0; + int k3 = 0; + if (representation.equals(tpBasis)) + { + // Trinomial basis representation + k1 = ((DERInteger)parameters.getObjectAt(2)).getValue(). + intValue(); + } + else + { + // Pentanomial basis representation + DERSequence pentanomial + = (DERSequence)parameters.getObjectAt(2); + k1 = ((DERInteger)pentanomial.getObjectAt(0)).getValue(). + intValue(); + k2 = ((DERInteger)pentanomial.getObjectAt(1)).getValue(). + intValue(); + k3 = ((DERInteger)pentanomial.getObjectAt(2)).getValue(). + intValue(); + } + X9FieldElement x9A = new X9FieldElement(m, k1, k2, k3, (ASN1OctetString)seq.getObjectAt(0)); + X9FieldElement x9B = new X9FieldElement(m, k1, k2, k3, (ASN1OctetString)seq.getObjectAt(1)); + // TODO Is it possible to get the order (n) and cofactor(h) too? + curve = new ECCurve.F2m(m, k1, k2, k3, x9A.getValue().toBigInteger(), x9B.getValue().toBigInteger()); + } + } + + if (seq.size() == 3) + { + seed = ((DERBitString)seq.getObjectAt(2)).getBytes(); + } + } + + private void setFieldIdentifier() + { + if (curve instanceof ECCurve.Fp) + { + fieldIdentifier = prime_field; + } + else if (curve instanceof ECCurve.F2m) + { + fieldIdentifier = characteristic_two_field; + } + else + { + throw new IllegalArgumentException("This type of ECCurve is not " + + "implemented"); + } + } + + public ECCurve getCurve() + { + return curve; + } + + public byte[] getSeed() + { + return seed; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  Curve ::= SEQUENCE {
+     *      a               FieldElement,
+     *      b               FieldElement,
+     *      seed            BIT STRING      OPTIONAL
+     *  }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + if (fieldIdentifier.equals(prime_field)) + { + v.add(new X9FieldElement(curve.getA()).getDERObject()); + v.add(new X9FieldElement(curve.getB()).getDERObject()); + } + else if (fieldIdentifier.equals(characteristic_two_field)) + { + v.add(new X9FieldElement(curve.getA()).getDERObject()); + v.add(new X9FieldElement(curve.getB()).getDERObject()); + } + + if (seed != null) + { + v.add(new DERBitString(seed)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x9/X9ECParameters.java b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9ECParameters.java new file mode 100644 index 000000000..517cb9072 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9ECParameters.java @@ -0,0 +1,161 @@ +package com.google.bitcoin.bouncycastle.asn1.x9; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.math.ec.ECCurve; +import com.google.bitcoin.bouncycastle.math.ec.ECPoint; + +import java.math.BigInteger; + +/** + * ASN.1 def for Elliptic-Curve ECParameters structure. See + * X9.62, for further details. + */ +public class X9ECParameters + extends ASN1Encodable + implements X9ObjectIdentifiers +{ + private static final BigInteger ONE = BigInteger.valueOf(1); + + private X9FieldID fieldID; + private ECCurve curve; + private ECPoint g; + private BigInteger n; + private BigInteger h; + private byte[] seed; + + public X9ECParameters( + ASN1Sequence seq) + { + if (!(seq.getObjectAt(0) instanceof DERInteger) + || !((DERInteger)seq.getObjectAt(0)).getValue().equals(ONE)) + { + throw new IllegalArgumentException("bad version in X9ECParameters"); + } + + X9Curve x9c = new X9Curve( + new X9FieldID((ASN1Sequence)seq.getObjectAt(1)), + (ASN1Sequence)seq.getObjectAt(2)); + + this.curve = x9c.getCurve(); + this.g = new X9ECPoint(curve, (ASN1OctetString)seq.getObjectAt(3)).getPoint(); + this.n = ((DERInteger)seq.getObjectAt(4)).getValue(); + this.seed = x9c.getSeed(); + + if (seq.size() == 6) + { + this.h = ((DERInteger)seq.getObjectAt(5)).getValue(); + } + } + + public X9ECParameters( + ECCurve curve, + ECPoint g, + BigInteger n) + { + this(curve, g, n, ONE, null); + } + + public X9ECParameters( + ECCurve curve, + ECPoint g, + BigInteger n, + BigInteger h) + { + this(curve, g, n, h, null); + } + + public X9ECParameters( + ECCurve curve, + ECPoint g, + BigInteger n, + BigInteger h, + byte[] seed) + { + this.curve = curve; + this.g = g; + this.n = n; + this.h = h; + this.seed = seed; + + if (curve instanceof ECCurve.Fp) + { + this.fieldID = new X9FieldID(((ECCurve.Fp)curve).getQ()); + } + else + { + if (curve instanceof ECCurve.F2m) + { + ECCurve.F2m curveF2m = (ECCurve.F2m)curve; + this.fieldID = new X9FieldID(curveF2m.getM(), curveF2m.getK1(), + curveF2m.getK2(), curveF2m.getK3()); + } + } + } + + public ECCurve getCurve() + { + return curve; + } + + public ECPoint getG() + { + return g; + } + + public BigInteger getN() + { + return n; + } + + public BigInteger getH() + { + if (h == null) + { + return ONE; // TODO - this should be calculated, it will cause issues with custom curves. + } + + return h; + } + + public byte[] getSeed() + { + return seed; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  ECParameters ::= SEQUENCE {
+     *      version         INTEGER { ecpVer1(1) } (ecpVer1),
+     *      fieldID         FieldID {{FieldTypes}},
+     *      curve           X9Curve,
+     *      base            X9ECPoint,
+     *      order           INTEGER,
+     *      cofactor        INTEGER OPTIONAL
+     *  }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new DERInteger(1)); + v.add(fieldID); + v.add(new X9Curve(curve, seed)); + v.add(new X9ECPoint(g)); + v.add(new DERInteger(n)); + + if (h != null) + { + v.add(new DERInteger(h)); + } + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x9/X9ECParametersHolder.java b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9ECParametersHolder.java new file mode 100644 index 000000000..01f55ab5a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9ECParametersHolder.java @@ -0,0 +1,18 @@ +package com.google.bitcoin.bouncycastle.asn1.x9; + +public abstract class X9ECParametersHolder +{ + private X9ECParameters params; + + public X9ECParameters getParameters() + { + if (params == null) + { + params = createParameters(); + } + + return params; + } + + protected abstract X9ECParameters createParameters(); +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x9/X9ECPoint.java b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9ECPoint.java new file mode 100644 index 000000000..0bc3f97d9 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9ECPoint.java @@ -0,0 +1,48 @@ +package com.google.bitcoin.bouncycastle.asn1.x9; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.math.ec.ECCurve; +import com.google.bitcoin.bouncycastle.math.ec.ECPoint; + +/** + * class for describing an ECPoint as a DER object. + */ +public class X9ECPoint + extends ASN1Encodable +{ + ECPoint p; + + public X9ECPoint( + ECPoint p) + { + this.p = p; + } + + public X9ECPoint( + ECCurve c, + ASN1OctetString s) + { + this.p = c.decodePoint(s.getOctets()); + } + + public ECPoint getPoint() + { + return p; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *
+     *  ECPoint ::= OCTET STRING
+     * 
+ *

+ * Octet string produced using ECPoint.getEncoded(). + */ + public DERObject toASN1Object() + { + return new DEROctetString(p.getEncoded()); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x9/X9FieldElement.java b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9FieldElement.java new file mode 100644 index 000000000..feded2147 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9FieldElement.java @@ -0,0 +1,64 @@ +package com.google.bitcoin.bouncycastle.asn1.x9; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.math.ec.ECFieldElement; + +/** + * class for processing an FieldElement as a DER object. + */ +public class X9FieldElement + extends ASN1Encodable +{ + protected ECFieldElement f; + + private static X9IntegerConverter converter = new X9IntegerConverter(); + + public X9FieldElement(ECFieldElement f) + { + this.f = f; + } + + public X9FieldElement(BigInteger p, ASN1OctetString s) + { + this(new ECFieldElement.Fp(p, new BigInteger(1, s.getOctets()))); + } + + public X9FieldElement(int m, int k1, int k2, int k3, ASN1OctetString s) + { + this(new ECFieldElement.F2m(m, k1, k2, k3, new BigInteger(1, s.getOctets()))); + } + + public ECFieldElement getValue() + { + return f; + } + + /** + * Produce an object suitable for an ASN1OutputStream. + *

+     *  FieldElement ::= OCTET STRING
+     * 
+ *

+ *

    + *
  1. if q is an odd prime then the field element is + * processed as an Integer and converted to an octet string + * according to x 9.62 4.3.1.
  2. + *
  3. if q is 2m then the bit string + * contained in the field element is converted into an octet + * string with the same ordering padded at the front if necessary. + *
  4. + *
+ */ + public DERObject toASN1Object() + { + int byteCount = converter.getByteLength(f); + byte[] paddedBigInteger = converter.integerToBytes(f.toBigInteger(), byteCount); + + return new DEROctetString(paddedBigInteger); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x9/X9FieldID.java b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9FieldID.java new file mode 100644 index 000000000..c32e5d527 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9FieldID.java @@ -0,0 +1,109 @@ +package com.google.bitcoin.bouncycastle.asn1.x9; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.asn1.ASN1Encodable; +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; + +/** + * ASN.1 def for Elliptic-Curve Field ID structure. See + * X9.62, for further details. + */ +public class X9FieldID + extends ASN1Encodable + implements X9ObjectIdentifiers +{ + private DERObjectIdentifier id; + private DERObject parameters; + + /** + * Constructor for elliptic curves over prime fields + * F2. + * @param primeP The prime p defining the prime field. + */ + public X9FieldID(BigInteger primeP) + { + this.id = prime_field; + this.parameters = new DERInteger(primeP); + } + + /** + * Constructor for elliptic curves over binary fields + * F2m. + * @param m The exponent m of + * F2m. + * @param k1 The integer k1 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z). + * @param k2 The integer k2 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z). + * @param k3 The integer k3 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z).. + */ + public X9FieldID(int m, int k1, int k2, int k3) + { + this.id = characteristic_two_field; + ASN1EncodableVector fieldIdParams = new ASN1EncodableVector(); + fieldIdParams.add(new DERInteger(m)); + + if (k2 == 0) + { + fieldIdParams.add(tpBasis); + fieldIdParams.add(new DERInteger(k1)); + } + else + { + fieldIdParams.add(ppBasis); + ASN1EncodableVector pentanomialParams = new ASN1EncodableVector(); + pentanomialParams.add(new DERInteger(k1)); + pentanomialParams.add(new DERInteger(k2)); + pentanomialParams.add(new DERInteger(k3)); + fieldIdParams.add(new DERSequence(pentanomialParams)); + } + + this.parameters = new DERSequence(fieldIdParams); + } + + public X9FieldID( + ASN1Sequence seq) + { + this.id = (DERObjectIdentifier)seq.getObjectAt(0); + this.parameters = (DERObject)seq.getObjectAt(1); + } + + public DERObjectIdentifier getIdentifier() + { + return id; + } + + public DERObject getParameters() + { + return parameters; + } + + /** + * Produce a DER encoding of the following structure. + *
+     *  FieldID ::= SEQUENCE {
+     *      fieldType       FIELD-ID.&id({IOSet}),
+     *      parameters      FIELD-ID.&Type({IOSet}{@fieldType})
+     *  }
+     * 
+ */ + public DERObject toASN1Object() + { + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(this.id); + v.add(this.parameters); + + return new DERSequence(v); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x9/X9IntegerConverter.java b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9IntegerConverter.java new file mode 100644 index 000000000..dd0b4f52d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9IntegerConverter.java @@ -0,0 +1,47 @@ +package com.google.bitcoin.bouncycastle.asn1.x9; + +import com.google.bitcoin.bouncycastle.math.ec.ECCurve; +import com.google.bitcoin.bouncycastle.math.ec.ECFieldElement; + +import java.math.BigInteger; + +public class X9IntegerConverter +{ + public int getByteLength( + ECCurve c) + { + return (c.getFieldSize() + 7) / 8; + } + + public int getByteLength( + ECFieldElement fe) + { + return (fe.getFieldSize() + 7) / 8; + } + + public byte[] integerToBytes( + BigInteger s, + int qLength) + { + byte[] bytes = s.toByteArray(); + + if (qLength < bytes.length) + { + byte[] tmp = new byte[qLength]; + + System.arraycopy(bytes, bytes.length - tmp.length, tmp, 0, tmp.length); + + return tmp; + } + else if (qLength > bytes.length) + { + byte[] tmp = new byte[qLength]; + + System.arraycopy(bytes, 0, tmp, tmp.length - bytes.length, bytes.length); + + return tmp; + } + + return bytes; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/asn1/x9/X9ObjectIdentifiers.java b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9ObjectIdentifiers.java new file mode 100644 index 000000000..427f024b4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/asn1/x9/X9ObjectIdentifiers.java @@ -0,0 +1,142 @@ +package com.google.bitcoin.bouncycastle.asn1.x9; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; + +public interface X9ObjectIdentifiers +{ + // + // X9.62 + // + // ansi-X9-62 OBJECT IDENTIFIER ::= { iso(1) member-body(2) + // us(840) ansi-x962(10045) } + // + static final String ansi_X9_62 = "1.2.840.10045"; + static final String id_fieldType = ansi_X9_62 + ".1"; + + static final DERObjectIdentifier prime_field + = new DERObjectIdentifier(id_fieldType + ".1"); + + static final DERObjectIdentifier characteristic_two_field + = new DERObjectIdentifier(id_fieldType + ".2"); + + static final DERObjectIdentifier gnBasis + = new DERObjectIdentifier(id_fieldType + ".2.3.1"); + + static final DERObjectIdentifier tpBasis + = new DERObjectIdentifier(id_fieldType + ".2.3.2"); + + static final DERObjectIdentifier ppBasis + = new DERObjectIdentifier(id_fieldType + ".2.3.3"); + + static final String id_ecSigType = ansi_X9_62 + ".4"; + + static final DERObjectIdentifier ecdsa_with_SHA1 + = new DERObjectIdentifier(id_ecSigType + ".1"); + + static final String id_publicKeyType = ansi_X9_62 + ".2"; + + static final DERObjectIdentifier id_ecPublicKey + = new DERObjectIdentifier(id_publicKeyType + ".1"); + + static final DERObjectIdentifier ecdsa_with_SHA2 + = new DERObjectIdentifier(id_ecSigType + ".3"); + + static final DERObjectIdentifier ecdsa_with_SHA224 + = new DERObjectIdentifier(ecdsa_with_SHA2 + ".1"); + + static final DERObjectIdentifier ecdsa_with_SHA256 + = new DERObjectIdentifier(ecdsa_with_SHA2 + ".2"); + + static final DERObjectIdentifier ecdsa_with_SHA384 + = new DERObjectIdentifier(ecdsa_with_SHA2 + ".3"); + + static final DERObjectIdentifier ecdsa_with_SHA512 + = new DERObjectIdentifier(ecdsa_with_SHA2 + ".4"); + + // + // named curves + // + static final String ellipticCurve = ansi_X9_62 + ".3"; + + // + // Two Curves + // + static final String cTwoCurve = ellipticCurve + ".0"; + + static final DERObjectIdentifier c2pnb163v1 = new DERObjectIdentifier(cTwoCurve + ".1"); + static final DERObjectIdentifier c2pnb163v2 = new DERObjectIdentifier(cTwoCurve + ".2"); + static final DERObjectIdentifier c2pnb163v3 = new DERObjectIdentifier(cTwoCurve + ".3"); + static final DERObjectIdentifier c2pnb176w1 = new DERObjectIdentifier(cTwoCurve + ".4"); + static final DERObjectIdentifier c2tnb191v1 = new DERObjectIdentifier(cTwoCurve + ".5"); + static final DERObjectIdentifier c2tnb191v2 = new DERObjectIdentifier(cTwoCurve + ".6"); + static final DERObjectIdentifier c2tnb191v3 = new DERObjectIdentifier(cTwoCurve + ".7"); + static final DERObjectIdentifier c2onb191v4 = new DERObjectIdentifier(cTwoCurve + ".8"); + static final DERObjectIdentifier c2onb191v5 = new DERObjectIdentifier(cTwoCurve + ".9"); + static final DERObjectIdentifier c2pnb208w1 = new DERObjectIdentifier(cTwoCurve + ".10"); + static final DERObjectIdentifier c2tnb239v1 = new DERObjectIdentifier(cTwoCurve + ".11"); + static final DERObjectIdentifier c2tnb239v2 = new DERObjectIdentifier(cTwoCurve + ".12"); + static final DERObjectIdentifier c2tnb239v3 = new DERObjectIdentifier(cTwoCurve + ".13"); + static final DERObjectIdentifier c2onb239v4 = new DERObjectIdentifier(cTwoCurve + ".14"); + static final DERObjectIdentifier c2onb239v5 = new DERObjectIdentifier(cTwoCurve + ".15"); + static final DERObjectIdentifier c2pnb272w1 = new DERObjectIdentifier(cTwoCurve + ".16"); + static final DERObjectIdentifier c2pnb304w1 = new DERObjectIdentifier(cTwoCurve + ".17"); + static final DERObjectIdentifier c2tnb359v1 = new DERObjectIdentifier(cTwoCurve + ".18"); + static final DERObjectIdentifier c2pnb368w1 = new DERObjectIdentifier(cTwoCurve + ".19"); + static final DERObjectIdentifier c2tnb431r1 = new DERObjectIdentifier(cTwoCurve + ".20"); + + // + // Prime + // + static final String primeCurve = ellipticCurve + ".1"; + + static final DERObjectIdentifier prime192v1 = new DERObjectIdentifier(primeCurve + ".1"); + static final DERObjectIdentifier prime192v2 = new DERObjectIdentifier(primeCurve + ".2"); + static final DERObjectIdentifier prime192v3 = new DERObjectIdentifier(primeCurve + ".3"); + static final DERObjectIdentifier prime239v1 = new DERObjectIdentifier(primeCurve + ".4"); + static final DERObjectIdentifier prime239v2 = new DERObjectIdentifier(primeCurve + ".5"); + static final DERObjectIdentifier prime239v3 = new DERObjectIdentifier(primeCurve + ".6"); + static final DERObjectIdentifier prime256v1 = new DERObjectIdentifier(primeCurve + ".7"); + + // + // Diffie-Hellman + // + // dhpublicnumber OBJECT IDENTIFIER ::= { iso(1) member-body(2) + // us(840) ansi-x942(10046) number-type(2) 1 } + // + static final DERObjectIdentifier dhpublicnumber = new DERObjectIdentifier("1.2.840.10046.2.1"); + + // + // DSA + // + // dsapublicnumber OBJECT IDENTIFIER ::= { iso(1) member-body(2) + // us(840) ansi-x957(10040) number-type(4) 1 } + static final DERObjectIdentifier id_dsa = new DERObjectIdentifier("1.2.840.10040.4.1"); + + /** + * id-dsa-with-sha1 OBJECT IDENTIFIER ::= { iso(1) member-body(2) + * us(840) x9-57 (10040) x9cm(4) 3 } + */ + public static final DERObjectIdentifier id_dsa_with_sha1 = new DERObjectIdentifier("1.2.840.10040.4.3"); + + /** + * X9.63 + */ + public static final DERObjectIdentifier x9_63_scheme = new DERObjectIdentifier("1.3.133.16.840.63.0"); + public static final DERObjectIdentifier dhSinglePass_stdDH_sha1kdf_scheme = new DERObjectIdentifier(x9_63_scheme + ".2"); + public static final DERObjectIdentifier dhSinglePass_cofactorDH_sha1kdf_scheme = new DERObjectIdentifier(x9_63_scheme + ".3"); + public static final DERObjectIdentifier mqvSinglePass_sha1kdf_scheme = new DERObjectIdentifier(x9_63_scheme + ".16"); + + /** + * X9.42 + */ + public static final DERObjectIdentifier x9_42_schemes = new DERObjectIdentifier("1.2.840.10046.3"); + public static final DERObjectIdentifier dhStatic = new DERObjectIdentifier(x9_42_schemes + ".1"); + public static final DERObjectIdentifier dhEphem = new DERObjectIdentifier(x9_42_schemes + ".2"); + public static final DERObjectIdentifier dhOneFlow = new DERObjectIdentifier(x9_42_schemes + ".3"); + public static final DERObjectIdentifier dhHybrid1 = new DERObjectIdentifier(x9_42_schemes + ".4"); + public static final DERObjectIdentifier dhHybrid2 = new DERObjectIdentifier(x9_42_schemes + ".5"); + public static final DERObjectIdentifier dhHybridOneFlow = new DERObjectIdentifier(x9_42_schemes + ".6"); + public static final DERObjectIdentifier mqv2 = new DERObjectIdentifier(x9_42_schemes + ".7"); + public static final DERObjectIdentifier mqv1 = new DERObjectIdentifier(x9_42_schemes + ".8"); +} + diff --git a/src/com/google/bitcoin/bouncycastle/bcpg/ArmoredInputStream.java b/src/com/google/bitcoin/bouncycastle/bcpg/ArmoredInputStream.java new file mode 100644 index 000000000..709c481ac --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/ArmoredInputStream.java @@ -0,0 +1,471 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import java.io.*; +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/src/com/google/bitcoin/bouncycastle/bcpg/ArmoredOutputStream.java b/src/com/google/bitcoin/bouncycastle/bcpg/ArmoredOutputStream.java new file mode 100644 index 000000000..dad7fcf03 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/ArmoredOutputStream.java @@ -0,0 +1,410 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import java.io.*; +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/src/com/google/bitcoin/bouncycastle/bcpg/BCPGInputStream.java b/src/com/google/bitcoin/bouncycastle/bcpg/BCPGInputStream.java new file mode 100644 index 000000000..1eb84bcd5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/BCPGInputStream.java @@ -0,0 +1,385 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import com.google.bitcoin.bouncycastle.util.io.Streams; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +/** + * 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 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/src/com/google/bitcoin/bouncycastle/bcpg/BCPGKey.java b/src/com/google/bitcoin/bouncycastle/bcpg/BCPGKey.java new file mode 100644 index 000000000..aa325654f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/BCPGKey.java @@ -0,0 +1,24 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/BCPGObject.java b/src/com/google/bitcoin/bouncycastle/bcpg/BCPGObject.java new file mode 100644 index 000000000..8f29d16d1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/BCPGObject.java @@ -0,0 +1,24 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/BCPGOutputStream.java b/src/com/google/bitcoin/bouncycastle/bcpg/BCPGOutputStream.java new file mode 100644 index 000000000..e052e06a1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/BCPGOutputStream.java @@ -0,0 +1,360 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import java.io.*; + +/** + * 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/src/com/google/bitcoin/bouncycastle/bcpg/CRC24.java b/src/com/google/bitcoin/bouncycastle/bcpg/CRC24.java new file mode 100644 index 000000000..6cd08f351 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/CRC24.java @@ -0,0 +1,37 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/CompressedDataPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/CompressedDataPacket.java new file mode 100644 index 000000000..c61df50b6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/CompressedDataPacket.java @@ -0,0 +1,31 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import java.io.*; + +/** + * 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/src/com/google/bitcoin/bouncycastle/bcpg/CompressionAlgorithmTags.java b/src/com/google/bitcoin/bouncycastle/bcpg/CompressionAlgorithmTags.java new file mode 100644 index 000000000..c25be5f9d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/CompressionAlgorithmTags.java @@ -0,0 +1,12 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/ContainedPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/ContainedPacket.java new file mode 100644 index 000000000..6a70be410 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/ContainedPacket.java @@ -0,0 +1,25 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import java.io.*; + +/** + * 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/src/com/google/bitcoin/bouncycastle/bcpg/DSAPublicBCPGKey.java b/src/com/google/bitcoin/bouncycastle/bcpg/DSAPublicBCPGKey.java new file mode 100644 index 000000000..17e81007c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/DSAPublicBCPGKey.java @@ -0,0 +1,116 @@ +package com.google.bitcoin.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 com.google.bitcoin.bouncycastle.bcpg.BCPGKey#getFormat() + */ + public String getFormat() + { + return "PGP"; + } + + /** + * return the standard PGP encoding of the key. + * + * @see com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/DSASecretBCPGKey.java b/src/com/google/bitcoin/bouncycastle/bcpg/DSASecretBCPGKey.java new file mode 100644 index 000000000..cbcff0e59 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/DSASecretBCPGKey.java @@ -0,0 +1,82 @@ +package com.google.bitcoin.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 com.google.bitcoin.bouncycastle.bcpg.BCPGKey#getFormat() + */ + public String getFormat() + { + return "PGP"; + } + + /** + * return the standard PGP encoding of the key. + * + * @see com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/ElGamalPublicBCPGKey.java b/src/com/google/bitcoin/bouncycastle/bcpg/ElGamalPublicBCPGKey.java new file mode 100644 index 000000000..c135f4e2c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/ElGamalPublicBCPGKey.java @@ -0,0 +1,93 @@ +package com.google.bitcoin.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 com.google.bitcoin.bouncycastle.bcpg.BCPGKey#getFormat() + */ + public String getFormat() + { + return "PGP"; + } + + /** + * return the standard PGP encoding of the key. + * + * @see com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/ElGamalSecretBCPGKey.java b/src/com/google/bitcoin/bouncycastle/bcpg/ElGamalSecretBCPGKey.java new file mode 100644 index 000000000..6fba5ca7c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/ElGamalSecretBCPGKey.java @@ -0,0 +1,79 @@ +package com.google.bitcoin.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 com.google.bitcoin.bouncycastle.bcpg.BCPGKey#getFormat() + */ + public String getFormat() + { + return "PGP"; + } + + public BigInteger getX() + { + return x.getValue(); + } + + /** + * return the standard PGP encoding of the key. + * + * @see com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/ExperimentalPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/ExperimentalPacket.java new file mode 100644 index 000000000..e820c6530 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/ExperimentalPacket.java @@ -0,0 +1,64 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import java.io.*; + +/** + * 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; + + if (in.available() != 0) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(in.available()); + + int b; + while ((b = in.read()) >= 0) + { + bOut.write(b); + } + + contents = bOut.toByteArray(); + } + else + { + contents = new byte[0]; + } + } + + public int getTag() + { + return tag; + } + + public byte[] getContents() + { + byte[] tmp = new byte[contents.length]; + + System.arraycopy(contents, 0, tmp, 0, tmp.length); + + return tmp; + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writePacket(tag, contents, true); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/bcpg/HashAlgorithmTags.java b/src/com/google/bitcoin/bouncycastle/bcpg/HashAlgorithmTags.java new file mode 100644 index 000000000..6cc2037a0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/HashAlgorithmTags.java @@ -0,0 +1,20 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/InputStreamPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/InputStreamPacket.java new file mode 100644 index 000000000..f04e2fce2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/InputStreamPacket.java @@ -0,0 +1,26 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/LiteralDataPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/LiteralDataPacket.java new file mode 100644 index 000000000..bae8e37fd --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/LiteralDataPacket.java @@ -0,0 +1,74 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import java.io.IOException; + +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/MPInteger.java b/src/com/google/bitcoin/bouncycastle/bcpg/MPInteger.java new file mode 100644 index 000000000..33e59911c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/MPInteger.java @@ -0,0 +1,62 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/MarkerPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/MarkerPacket.java new file mode 100644 index 000000000..bc32278e6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/MarkerPacket.java @@ -0,0 +1,28 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/ModDetectionCodePacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/ModDetectionCodePacket.java new file mode 100644 index 000000000..aa732c963 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/ModDetectionCodePacket.java @@ -0,0 +1,45 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/OnePassSignaturePacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/OnePassSignaturePacket.java new file mode 100644 index 000000000..0f3d06a80 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/OnePassSignaturePacket.java @@ -0,0 +1,115 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/OutputStreamPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/OutputStreamPacket.java new file mode 100644 index 000000000..edf622e19 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/OutputStreamPacket.java @@ -0,0 +1,18 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/Packet.java b/src/com/google/bitcoin/bouncycastle/bcpg/Packet.java new file mode 100644 index 000000000..f869a0098 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/Packet.java @@ -0,0 +1,9 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +/** + */ +public class Packet + implements PacketTags +{ + +} diff --git a/src/com/google/bitcoin/bouncycastle/bcpg/PacketTags.java b/src/com/google/bitcoin/bouncycastle/bcpg/PacketTags.java new file mode 100644 index 000000000..abc43483e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/PacketTags.java @@ -0,0 +1,31 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/PublicKeyAlgorithmTags.java b/src/com/google/bitcoin/bouncycastle/bcpg/PublicKeyAlgorithmTags.java new file mode 100644 index 000000000..7c8553dfc --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/PublicKeyAlgorithmTags.java @@ -0,0 +1,29 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/PublicKeyEncSessionPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/PublicKeyEncSessionPacket.java new file mode 100644 index 000000000..f622acbb0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/PublicKeyEncSessionPacket.java @@ -0,0 +1,112 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/PublicKeyPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/PublicKeyPacket.java new file mode 100644 index 000000000..98f98da06 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/PublicKeyPacket.java @@ -0,0 +1,126 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/PublicSubkeyPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/PublicSubkeyPacket.java new file mode 100644 index 000000000..d55bcc763 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/PublicSubkeyPacket.java @@ -0,0 +1,40 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/RSAPublicBCPGKey.java b/src/com/google/bitcoin/bouncycastle/bcpg/RSAPublicBCPGKey.java new file mode 100644 index 000000000..25c11bc50 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/RSAPublicBCPGKey.java @@ -0,0 +1,91 @@ +package com.google.bitcoin.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 com.google.bitcoin.bouncycastle.bcpg.BCPGKey#getFormat() + */ + public String getFormat() + { + return "PGP"; + } + + /** + * return the standard PGP encoding of the key. + * + * @see com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/RSASecretBCPGKey.java b/src/com/google/bitcoin/bouncycastle/bcpg/RSASecretBCPGKey.java new file mode 100644 index 000000000..feffe36f8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/RSASecretBCPGKey.java @@ -0,0 +1,176 @@ +package com.google.bitcoin.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 com.google.bitcoin.bouncycastle.bcpg.BCPGKey#getFormat() + */ + public String getFormat() + { + return "PGP"; + } + + /** + * return the standard PGP encoding of the key. + * + * @see com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/S2K.java b/src/com/google/bitcoin/bouncycastle/bcpg/S2K.java new file mode 100644 index 000000000..b5b49d58b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/S2K.java @@ -0,0 +1,149 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import java.io.*; + +/** + * 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/src/com/google/bitcoin/bouncycastle/bcpg/SecretKeyPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/SecretKeyPacket.java new file mode 100644 index 000000000..c6f74d9af --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/SecretKeyPacket.java @@ -0,0 +1,189 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import java.io.*; + +/** + * 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); + } + } + + if (in.available() != 0) + { + secKeyData = new byte[in.available()]; + + in.readFully(secKeyData); + } + } + + /** + * + * @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/src/com/google/bitcoin/bouncycastle/bcpg/SecretSubkeyPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/SecretSubkeyPacket.java new file mode 100644 index 000000000..daec8cdee --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/SecretSubkeyPacket.java @@ -0,0 +1,58 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/SignaturePacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/SignaturePacket.java new file mode 100644 index 000000000..4f2d6f2d4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/SignaturePacket.java @@ -0,0 +1,515 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import com.google.bitcoin.bouncycastle.bcpg.sig.IssuerKeyID; +import com.google.bitcoin.bouncycastle.bcpg.sig.SignatureCreationTime; +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/SignatureSubpacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/SignatureSubpacket.java new file mode 100644 index 000000000..b40d42a20 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/SignatureSubpacket.java @@ -0,0 +1,80 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import java.io.*; + +/** + * 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/src/com/google/bitcoin/bouncycastle/bcpg/SignatureSubpacketInputStream.java b/src/com/google/bitcoin/bouncycastle/bcpg/SignatureSubpacketInputStream.java new file mode 100644 index 000000000..7f131d9f9 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/SignatureSubpacketInputStream.java @@ -0,0 +1,123 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import com.google.bitcoin.bouncycastle.bcpg.sig.Exportable; +import com.google.bitcoin.bouncycastle.bcpg.sig.IssuerKeyID; +import com.google.bitcoin.bouncycastle.bcpg.sig.KeyExpirationTime; +import com.google.bitcoin.bouncycastle.bcpg.sig.KeyFlags; +import com.google.bitcoin.bouncycastle.bcpg.sig.NotationData; +import com.google.bitcoin.bouncycastle.bcpg.sig.PreferredAlgorithms; +import com.google.bitcoin.bouncycastle.bcpg.sig.PrimaryUserID; +import com.google.bitcoin.bouncycastle.bcpg.sig.Revocable; +import com.google.bitcoin.bouncycastle.bcpg.sig.SignatureCreationTime; +import com.google.bitcoin.bouncycastle.bcpg.sig.SignatureExpirationTime; +import com.google.bitcoin.bouncycastle.bcpg.sig.SignerUserID; +import com.google.bitcoin.bouncycastle.bcpg.sig.TrustSignature; +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/SignatureSubpacketTags.java b/src/com/google/bitcoin/bouncycastle/bcpg/SignatureSubpacketTags.java new file mode 100644 index 000000000..e4548b8ea --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/SignatureSubpacketTags.java @@ -0,0 +1,32 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/SymmetricEncDataPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/SymmetricEncDataPacket.java new file mode 100644 index 000000000..d6b03f81c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/SymmetricEncDataPacket.java @@ -0,0 +1,14 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +/** + * Basic type for a symmetric key encrypted packet + */ +public class SymmetricEncDataPacket + extends InputStreamPacket +{ + public SymmetricEncDataPacket( + BCPGInputStream in) + { + super(in); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/bcpg/SymmetricEncIntegrityPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/SymmetricEncIntegrityPacket.java new file mode 100644 index 000000000..0b0ed43ee --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/SymmetricEncIntegrityPacket.java @@ -0,0 +1,20 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import java.io.*; + +/** + */ +public class SymmetricEncIntegrityPacket + extends InputStreamPacket +{ + int version; + + SymmetricEncIntegrityPacket( + BCPGInputStream in) + throws IOException + { + super(in); + + version = in.read(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/bcpg/SymmetricKeyAlgorithmTags.java b/src/com/google/bitcoin/bouncycastle/bcpg/SymmetricKeyAlgorithmTags.java new file mode 100644 index 000000000..84f07792b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/SymmetricKeyAlgorithmTags.java @@ -0,0 +1,19 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java new file mode 100644 index 000000000..6bbe2b8c9 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java @@ -0,0 +1,94 @@ +package com.google.bitcoin.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); + + if (in.available() != 0) + { + secKeyData = new byte[in.available()]; + in.readFully(secKeyData, 0, secKeyData.length); + } + } + + 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/src/com/google/bitcoin/bouncycastle/bcpg/TrustPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/TrustPacket.java new file mode 100644 index 000000000..ed564a4b9 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/TrustPacket.java @@ -0,0 +1,48 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/UserAttributePacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/UserAttributePacket.java new file mode 100644 index 000000000..790b648fe --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/UserAttributePacket.java @@ -0,0 +1,60 @@ +package com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/UserAttributeSubpacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/UserAttributeSubpacket.java new file mode 100644 index 000000000..3321b628e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/UserAttributeSubpacket.java @@ -0,0 +1,91 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/UserAttributeSubpacketInputStream.java b/src/com/google/bitcoin/bouncycastle/bcpg/UserAttributeSubpacketInputStream.java new file mode 100644 index 000000000..e0a05db25 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/UserAttributeSubpacketInputStream.java @@ -0,0 +1,116 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import java.io.*; + +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/UserAttributeSubpacketTags.java b/src/com/google/bitcoin/bouncycastle/bcpg/UserAttributeSubpacketTags.java new file mode 100644 index 000000000..7744be43e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/UserAttributeSubpacketTags.java @@ -0,0 +1,9 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +/** + * Basic PGP user attribute sub-packet tag types. + */ +public interface UserAttributeSubpacketTags +{ + public static final int IMAGE_ATTRIBUTE = 1; +} diff --git a/src/com/google/bitcoin/bouncycastle/bcpg/UserIDPacket.java b/src/com/google/bitcoin/bouncycastle/bcpg/UserIDPacket.java new file mode 100644 index 000000000..49f36a97f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/UserIDPacket.java @@ -0,0 +1,40 @@ +package com.google.bitcoin.bouncycastle.bcpg; + +import java.io.IOException; + +import com.google.bitcoin.bouncycastle.util.Strings; + +/** + * Basic type for a user ID packet. + */ +public class UserIDPacket + extends ContainedPacket +{ + private byte[] idData; + + public UserIDPacket( + BCPGInputStream in) + throws IOException + { + idData = new byte[in.available()]; + in.readFully(idData); + } + + 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/src/com/google/bitcoin/bouncycastle/bcpg/attr/ImageAttribute.java b/src/com/google/bitcoin/bouncycastle/bcpg/attr/ImageAttribute.java new file mode 100644 index 000000000..5fa14f0a0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/attr/ImageAttribute.java @@ -0,0 +1,77 @@ +package com.google.bitcoin.bouncycastle.bcpg.attr; + +import com.google.bitcoin.bouncycastle.bcpg.UserAttributeSubpacket; +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/sig/EmbeddedSignature.java b/src/com/google/bitcoin/bouncycastle/bcpg/sig/EmbeddedSignature.java new file mode 100644 index 000000000..b4ff1079b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/sig/EmbeddedSignature.java @@ -0,0 +1,18 @@ +package com.google.bitcoin.bouncycastle.bcpg.sig; + +import com.google.bitcoin.bouncycastle.bcpg.SignatureSubpacket; +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/sig/Exportable.java b/src/com/google/bitcoin/bouncycastle/bcpg/sig/Exportable.java new file mode 100644 index 000000000..243ae7d30 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/sig/Exportable.java @@ -0,0 +1,46 @@ +package com.google.bitcoin.bouncycastle.bcpg.sig; + +import com.google.bitcoin.bouncycastle.bcpg.SignatureSubpacket; +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/sig/IssuerKeyID.java b/src/com/google/bitcoin/bouncycastle/bcpg/sig/IssuerKeyID.java new file mode 100644 index 000000000..9abbd7487 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/sig/IssuerKeyID.java @@ -0,0 +1,50 @@ +package com.google.bitcoin.bouncycastle.bcpg.sig; + +import com.google.bitcoin.bouncycastle.bcpg.SignatureSubpacket; +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/sig/KeyExpirationTime.java b/src/com/google/bitcoin/bouncycastle/bcpg/sig/KeyExpirationTime.java new file mode 100644 index 000000000..6a89f8fe4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/sig/KeyExpirationTime.java @@ -0,0 +1,50 @@ +package com.google.bitcoin.bouncycastle.bcpg.sig; + +import com.google.bitcoin.bouncycastle.bcpg.SignatureSubpacket; +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/sig/KeyFlags.java b/src/com/google/bitcoin/bouncycastle/bcpg/sig/KeyFlags.java new file mode 100644 index 000000000..c4774c0a1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/sig/KeyFlags.java @@ -0,0 +1,73 @@ +package com.google.bitcoin.bouncycastle.bcpg.sig; + +import com.google.bitcoin.bouncycastle.bcpg.SignatureSubpacket; +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/sig/NotationData.java b/src/com/google/bitcoin/bouncycastle/bcpg/sig/NotationData.java new file mode 100644 index 000000000..7dc4f2c82 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/sig/NotationData.java @@ -0,0 +1,104 @@ +package com.google.bitcoin.bouncycastle.bcpg.sig; + +import com.google.bitcoin.bouncycastle.bcpg.SignatureSubpacket; +import com.google.bitcoin.bouncycastle.bcpg.SignatureSubpacketTags; +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/sig/PreferredAlgorithms.java b/src/com/google/bitcoin/bouncycastle/bcpg/sig/PreferredAlgorithms.java new file mode 100644 index 000000000..0d3ccec58 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/sig/PreferredAlgorithms.java @@ -0,0 +1,59 @@ +package com.google.bitcoin.bouncycastle.bcpg.sig; + +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/sig/PrimaryUserID.java b/src/com/google/bitcoin/bouncycastle/bcpg/sig/PrimaryUserID.java new file mode 100644 index 000000000..f2fad5ba2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/sig/PrimaryUserID.java @@ -0,0 +1,46 @@ +package com.google.bitcoin.bouncycastle.bcpg.sig; + +import com.google.bitcoin.bouncycastle.bcpg.SignatureSubpacket; +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/sig/Revocable.java b/src/com/google/bitcoin/bouncycastle/bcpg/sig/Revocable.java new file mode 100644 index 000000000..125727bdf --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/sig/Revocable.java @@ -0,0 +1,46 @@ +package com.google.bitcoin.bouncycastle.bcpg.sig; + +import com.google.bitcoin.bouncycastle.bcpg.SignatureSubpacket; +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/sig/SignatureCreationTime.java b/src/com/google/bitcoin/bouncycastle/bcpg/sig/SignatureCreationTime.java new file mode 100644 index 000000000..39a78b783 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/sig/SignatureCreationTime.java @@ -0,0 +1,48 @@ +package com.google.bitcoin.bouncycastle.bcpg.sig; + +import java.util.Date; + +import com.google.bitcoin.bouncycastle.bcpg.SignatureSubpacket; +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/sig/SignatureExpirationTime.java b/src/com/google/bitcoin/bouncycastle/bcpg/sig/SignatureExpirationTime.java new file mode 100644 index 000000000..a1c7568e7 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/sig/SignatureExpirationTime.java @@ -0,0 +1,48 @@ +package com.google.bitcoin.bouncycastle.bcpg.sig; + +import com.google.bitcoin.bouncycastle.bcpg.SignatureSubpacket; +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/sig/SignerUserID.java b/src/com/google/bitcoin/bouncycastle/bcpg/sig/SignerUserID.java new file mode 100644 index 000000000..dae8908e5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/sig/SignerUserID.java @@ -0,0 +1,50 @@ +package com.google.bitcoin.bouncycastle.bcpg.sig; + +import com.google.bitcoin.bouncycastle.bcpg.SignatureSubpacket; +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/bcpg/sig/TrustSignature.java b/src/com/google/bitcoin/bouncycastle/bcpg/sig/TrustSignature.java new file mode 100644 index 000000000..95cb104ef --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/bcpg/sig/TrustSignature.java @@ -0,0 +1,48 @@ +package com.google.bitcoin.bouncycastle.bcpg.sig; + +import com.google.bitcoin.bouncycastle.bcpg.SignatureSubpacket; +import com.google.bitcoin.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/src/com/google/bitcoin/bouncycastle/crypto/AsymmetricBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/AsymmetricBlockCipher.java new file mode 100644 index 000000000..73d826ead --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/AsymmetricBlockCipher.java @@ -0,0 +1,45 @@ +package com.google.bitcoin.bouncycastle.crypto; + + +/** + * base interface that a public/private key block cipher needs + * to conform to. + */ +public interface AsymmetricBlockCipher +{ + /** + * initialise the cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param param the key and other data required by the cipher. + */ + public void init(boolean forEncryption, CipherParameters param); + + /** + * returns the largest size an input block can be. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize(); + + /** + * returns the maximum size of the block produced by this cipher. + * + * @return maximum size of the output block produced by the cipher. + */ + public int getOutputBlockSize(); + + /** + * process the block of len bytes stored in in from offset inOff. + * + * @param in the input data + * @param inOff offset into the in array where the data starts + * @param len the length of the block to be processed. + * @return the resulting byte array of the encryption/decryption process. + * @exception InvalidCipherTextException data decrypts improperly. + * @exception DataLengthException the input data is too large for the cipher. + */ + public byte[] processBlock(byte[] in, int inOff, int len) + throws InvalidCipherTextException; +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/AsymmetricCipherKeyPair.java b/src/com/google/bitcoin/bouncycastle/crypto/AsymmetricCipherKeyPair.java new file mode 100644 index 000000000..55934c792 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/AsymmetricCipherKeyPair.java @@ -0,0 +1,44 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * a holding class for public/private parameter pairs. + */ +public class AsymmetricCipherKeyPair +{ + private CipherParameters publicParam; + private CipherParameters privateParam; + + /** + * basic constructor. + * + * @param publicParam a public key parameters object. + * @param privateParam the corresponding private key parameters. + */ + public AsymmetricCipherKeyPair( + CipherParameters publicParam, + CipherParameters privateParam) + { + this.publicParam = publicParam; + this.privateParam = privateParam; + } + + /** + * return the public key parameters. + * + * @return the public key parameters. + */ + public CipherParameters getPublic() + { + return publicParam; + } + + /** + * return the private key parameters. + * + * @return the private key parameters. + */ + public CipherParameters getPrivate() + { + return privateParam; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/AsymmetricCipherKeyPairGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/AsymmetricCipherKeyPairGenerator.java new file mode 100644 index 000000000..363f2768e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/AsymmetricCipherKeyPairGenerator.java @@ -0,0 +1,22 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * interface that a public/private key pair generator should conform to. + */ +public interface AsymmetricCipherKeyPairGenerator +{ + /** + * intialise the key pair generator. + * + * @param param the parameters the key pair is to be initialised with. + */ + public void init(KeyGenerationParameters param); + + /** + * return an AsymmetricCipherKeyPair containing the generated keys. + * + * @return an AsymmetricCipherKeyPair containing the generated keys. + */ + public AsymmetricCipherKeyPair generateKeyPair(); +} + diff --git a/src/com/google/bitcoin/bouncycastle/crypto/BasicAgreement.java b/src/com/google/bitcoin/bouncycastle/crypto/BasicAgreement.java new file mode 100644 index 000000000..ab53826af --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/BasicAgreement.java @@ -0,0 +1,21 @@ +package com.google.bitcoin.bouncycastle.crypto; + +import java.math.BigInteger; + +/** + * The basic interface that basic Diffie-Hellman implementations + * conforms to. + */ +public interface BasicAgreement +{ + /** + * initialise the agreement engine. + */ + public void init(CipherParameters param); + + /** + * given a public key from a given party calculate the next + * message in the agreement sequence. + */ + public BigInteger calculateAgreement(CipherParameters pubKey); +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/BlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/BlockCipher.java new file mode 100644 index 000000000..797b664d2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/BlockCipher.java @@ -0,0 +1,56 @@ +package com.google.bitcoin.bouncycastle.crypto; + + +/** + * Block cipher engines are expected to conform to this interface. + */ +public interface BlockCipher +{ + /** + * Initialise the cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException; + + /** + * Return the name of the algorithm the cipher implements. + * + * @return the name of the algorithm the cipher implements. + */ + public String getAlgorithmName(); + + /** + * Return the block size for this cipher (in bytes). + * + * @return the block size for this cipher in bytes. + */ + public int getBlockSize(); + + /** + * Process one block of input from the array in and write it to + * the out array. + * + * @param in the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param out the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock(byte[] in, int inOff, byte[] out, int outOff) + throws DataLengthException, IllegalStateException; + + /** + * Reset the cipher. After resetting the cipher is in the same state + * as it was after the last init (if there was one). + */ + public void reset(); +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/BufferedAsymmetricBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/BufferedAsymmetricBlockCipher.java new file mode 100644 index 000000000..b9aec9977 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/BufferedAsymmetricBlockCipher.java @@ -0,0 +1,171 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * a buffer wrapper for an asymmetric block cipher, allowing input + * to be accumulated in a piecemeal fashion until final processing. + */ +public class BufferedAsymmetricBlockCipher +{ + protected byte[] buf; + protected int bufOff; + + private final AsymmetricBlockCipher cipher; + + /** + * base constructor. + * + * @param cipher the cipher this buffering object wraps. + */ + public BufferedAsymmetricBlockCipher( + AsymmetricBlockCipher cipher) + { + this.cipher = cipher; + } + + /** + * return the underlying cipher for the buffer. + * + * @return the underlying cipher for the buffer. + */ + public AsymmetricBlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * return the amount of data sitting in the buffer. + * + * @return the amount of data sitting in the buffer. + */ + public int getBufferPosition() + { + return bufOff; + } + + /** + * initialise the buffer and the underlying cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + reset(); + + cipher.init(forEncryption, params); + + // + // we allow for an extra byte where people are using their own padding + // mechanisms on a raw cipher. + // + buf = new byte[cipher.getInputBlockSize() + (forEncryption ? 1 : 0)]; + bufOff = 0; + } + + /** + * returns the largest size an input block can be. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize() + { + return cipher.getInputBlockSize(); + } + + /** + * returns the maximum size of the block produced by this cipher. + * + * @return maximum size of the output block produced by the cipher. + */ + public int getOutputBlockSize() + { + return cipher.getOutputBlockSize(); + } + + /** + * add another byte for processing. + * + * @param in the input byte. + */ + public void processByte( + byte in) + { + if (bufOff >= buf.length) + { + throw new DataLengthException("attempt to process message too long for cipher"); + } + + buf[bufOff++] = in; + } + + /** + * add len bytes to the buffer for processing. + * + * @param in the input data + * @param inOff offset into the in array where the data starts + * @param len the length of the block to be processed. + */ + public void processBytes( + byte[] in, + int inOff, + int len) + { + if (len == 0) + { + return; + } + + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + if (bufOff + len > buf.length) + { + throw new DataLengthException("attempt to process message too long for cipher"); + } + + System.arraycopy(in, inOff, buf, bufOff, len); + bufOff += len; + } + + /** + * process the contents of the buffer using the underlying + * cipher. + * + * @return the result of the encryption/decryption process on the + * buffer. + * @exception InvalidCipherTextException if we are given a garbage block. + */ + public byte[] doFinal() + throws InvalidCipherTextException + { + byte[] out = cipher.processBlock(buf, 0, bufOff); + + reset(); + + return out; + } + + /** + * Reset the buffer and the underlying cipher. + */ + public void reset() + { + /* + * clean the buffer. + */ + if (buf != null) + { + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + } + + bufOff = 0; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/BufferedBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/BufferedBlockCipher.java new file mode 100644 index 000000000..b9f242e8a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/BufferedBlockCipher.java @@ -0,0 +1,307 @@ +package com.google.bitcoin.bouncycastle.crypto; + + +/** + * A wrapper class that allows block ciphers to be used to process data in + * a piecemeal fashion. The BufferedBlockCipher outputs a block only when the + * buffer is full and more data is being added, or on a doFinal. + *

+ * Note: in the case where the underlying cipher is either a CFB cipher or an + * OFB one the last block may not be a multiple of the block size. + */ +public class BufferedBlockCipher +{ + protected byte[] buf; + protected int bufOff; + + protected boolean forEncryption; + protected BlockCipher cipher; + + protected boolean partialBlockOkay; + protected boolean pgpCFB; + + /** + * constructor for subclasses + */ + protected BufferedBlockCipher() + { + } + + /** + * Create a buffered block cipher without padding. + * + * @param cipher the underlying block cipher this buffering object wraps. + */ + public BufferedBlockCipher( + BlockCipher cipher) + { + this.cipher = cipher; + + buf = new byte[cipher.getBlockSize()]; + bufOff = 0; + + // + // check if we can handle partial blocks on doFinal. + // + String name = cipher.getAlgorithmName(); + int idx = name.indexOf('/') + 1; + + pgpCFB = (idx > 0 && name.startsWith("PGP", idx)); + + if (pgpCFB) + { + partialBlockOkay = true; + } + else + { + partialBlockOkay = (idx > 0 && (name.startsWith("CFB", idx) || name.startsWith("OFB", idx) || name.startsWith("OpenPGP", idx) || name.startsWith("SIC", idx) || name.startsWith("GCTR", idx))); + } + } + + /** + * return the cipher this object wraps. + * + * @return the cipher this object wraps. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * initialise the cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + throws IllegalArgumentException + { + this.forEncryption = forEncryption; + + reset(); + + cipher.init(forEncryption, params); + } + + /** + * return the blocksize for the underlying cipher. + * + * @return the blocksize for the underlying cipher. + */ + public int getBlockSize() + { + return cipher.getBlockSize(); + } + + /** + * return the size of the output buffer required for an update + * an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update + * with len bytes of input. + */ + public int getUpdateOutputSize( + int len) + { + int total = len + bufOff; + int leftOver; + + if (pgpCFB) + { + leftOver = total % buf.length - (cipher.getBlockSize() + 2); + } + else + { + leftOver = total % buf.length; + } + + return total - leftOver; + } + + /** + * return the size of the output buffer required for an update plus a + * doFinal with an input of 'length' bytes. + * + * @param length the length of the input. + * @return the space required to accommodate a call to update and doFinal + * with 'length' bytes of input. + */ + public int getOutputSize( + int length) + { + // Note: Can assume partialBlockOkay is true for purposes of this calculation + return length + bufOff; + } + + /** + * process a single byte, producing an output block if neccessary. + * + * @param in the input byte. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processByte( + byte in, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + int resultLen = 0; + + buf[bufOff++] = in; + + if (bufOff == buf.length) + { + resultLen = cipher.processBlock(buf, 0, out, outOff); + bufOff = 0; + } + + return resultLen; + } + + /** + * process an array of bytes, producing output if necessary. + * + * @param in the input byte array. + * @param inOff the offset at which the input data starts. + * @param len the number of bytes to be copied out of the input array. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = getBlockSize(); + int length = getUpdateOutputSize(len); + + if (length > 0) + { + if ((outOff + length) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + } + + int resultLen = 0; + int gapLen = buf.length - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += cipher.processBlock(buf, 0, out, outOff); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > buf.length) + { + resultLen += cipher.processBlock(in, inOff, out, outOff + resultLen); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + + if (bufOff == buf.length) + { + resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen); + bufOff = 0; + } + + return resultLen; + } + + /** + * Process the last block in the buffer. + * + * @param out the array the block currently being held is copied into. + * @param outOff the offset at which the copying starts. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there is insufficient space in out for + * the output, or the input is not block size aligned and should be. + * @exception IllegalStateException if the underlying cipher is not + * initialised. + * @exception InvalidCipherTextException if padding is expected and not found. + * @exception DataLengthException if the input is not block size + * aligned. + */ + public int doFinal( + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException, InvalidCipherTextException + { + int resultLen = 0; + + if (outOff + bufOff > out.length) + { + throw new DataLengthException("output buffer too short for doFinal()"); + } + + if (bufOff != 0 && partialBlockOkay) + { + cipher.processBlock(buf, 0, buf, 0); + resultLen = bufOff; + bufOff = 0; + System.arraycopy(buf, 0, out, outOff, resultLen); + } + else if (bufOff != 0) + { + throw new DataLengthException("data not block size aligned"); + } + + reset(); + + return resultLen; + } + + /** + * Reset the buffer and cipher. After resetting the object is in the same + * state as it was after the last init (if there was one). + */ + public void reset() + { + // + // clean the buffer. + // + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + + bufOff = 0; + + // + // reset the underlying cipher. + // + cipher.reset(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/CipherKeyGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/CipherKeyGenerator.java new file mode 100644 index 000000000..0912f7da5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/CipherKeyGenerator.java @@ -0,0 +1,38 @@ +package com.google.bitcoin.bouncycastle.crypto; + +import java.security.SecureRandom; + +/** + * The base class for symmetric, or secret, cipher key generators. + */ +public class CipherKeyGenerator +{ + protected SecureRandom random; + protected int strength; + + /** + * initialise the key generator. + * + * @param param the parameters to be used for key generation + */ + public void init( + KeyGenerationParameters param) + { + this.random = param.getRandom(); + this.strength = (param.getStrength() + 7) / 8; + } + + /** + * generate a secret key. + * + * @return a byte array containing the key value. + */ + public byte[] generateKey() + { + byte[] key = new byte[strength]; + + random.nextBytes(key); + + return key; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/CipherParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/CipherParameters.java new file mode 100644 index 000000000..e4be0ce01 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/CipherParameters.java @@ -0,0 +1,8 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * all parameter classes implement this. + */ +public interface CipherParameters +{ +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/CryptoException.java b/src/com/google/bitcoin/bouncycastle/crypto/CryptoException.java new file mode 100644 index 000000000..6fd27fb06 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/CryptoException.java @@ -0,0 +1,26 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * the foundation class for the hard exceptions thrown by the crypto packages. + */ +public class CryptoException + extends Exception +{ + /** + * base constructor. + */ + public CryptoException() + { + } + + /** + * create a CryptoException with the given message. + * + * @param message the message to be carried with the exception. + */ + public CryptoException( + String message) + { + super(message); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/DSA.java b/src/com/google/bitcoin/bouncycastle/crypto/DSA.java new file mode 100644 index 000000000..73f609c64 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/DSA.java @@ -0,0 +1,36 @@ +package com.google.bitcoin.bouncycastle.crypto; + +import java.math.BigInteger; + +/** + * interface for classes implementing algorithms modeled similar to the Digital Signature Alorithm. + */ +public interface DSA +{ + /** + * initialise the signer for signature generation or signature + * verification. + * + * @param forSigning true if we are generating a signature, false + * otherwise. + * @param param key parameters for signature generation. + */ + public void init(boolean forSigning, CipherParameters param); + + /** + * sign the passed in message (usually the output of a hash function). + * + * @param message the message to be signed. + * @return two big integers representing the r and s values respectively. + */ + public BigInteger[] generateSignature(byte[] message); + + /** + * verify the message message against the signature values r and s. + * + * @param message the message that was supposed to have been signed. + * @param r the r signature value. + * @param s the s signature value. + */ + public boolean verifySignature(byte[] message, BigInteger r, BigInteger s); +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/DataLengthException.java b/src/com/google/bitcoin/bouncycastle/crypto/DataLengthException.java new file mode 100644 index 000000000..f4e767cb2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/DataLengthException.java @@ -0,0 +1,29 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * this exception is thrown if a buffer that is meant to have output + * copied into it turns out to be too short, or if we've been given + * insufficient input. In general this exception will get thrown rather + * than an ArrayOutOfBounds exception. + */ +public class DataLengthException + extends RuntimeCryptoException +{ + /** + * base constructor. + */ + public DataLengthException() + { + } + + /** + * create a DataLengthException with the given message. + * + * @param message the message to be carried with the exception. + */ + public DataLengthException( + String message) + { + super(message); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/DerivationFunction.java b/src/com/google/bitcoin/bouncycastle/crypto/DerivationFunction.java new file mode 100644 index 000000000..1a77f1ac5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/DerivationFunction.java @@ -0,0 +1,17 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * base interface for general purpose byte derivation functions. + */ +public interface DerivationFunction +{ + public void init(DerivationParameters param); + + /** + * return the message digest used as the basis for the function + */ + public Digest getDigest(); + + public int generateBytes(byte[] out, int outOff, int len) + throws DataLengthException, IllegalArgumentException; +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/DerivationParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/DerivationParameters.java new file mode 100644 index 000000000..ec5aa9f27 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/DerivationParameters.java @@ -0,0 +1,8 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * Parameters for key/byte stream derivation classes + */ +public interface DerivationParameters +{ +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/Digest.java b/src/com/google/bitcoin/bouncycastle/crypto/Digest.java new file mode 100644 index 000000000..21eddd0cf --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/Digest.java @@ -0,0 +1,51 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * interface that a message digest conforms to. + */ +public interface Digest +{ + /** + * return the algorithm name + * + * @return the algorithm name + */ + public String getAlgorithmName(); + + /** + * return the size, in bytes, of the digest produced by this message digest. + * + * @return the size, in bytes, of the digest produced by this message digest. + */ + public int getDigestSize(); + + /** + * update the message digest with a single byte. + * + * @param in the input byte to be entered. + */ + public void update(byte in); + + /** + * update the message digest with a block of bytes. + * + * @param in the byte array containing the data. + * @param inOff the offset into the byte array where the data starts. + * @param len the length of the data. + */ + public void update(byte[] in, int inOff, int len); + + /** + * close the digest, producing the final digest value. The doFinal + * call leaves the digest reset. + * + * @param out the array the digest is to be copied into. + * @param outOff the offset into the out array the digest is to start at. + */ + public int doFinal(byte[] out, int outOff); + + /** + * reset the digest back to it's initial state. + */ + public void reset(); +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/ExtendedDigest.java b/src/com/google/bitcoin/bouncycastle/crypto/ExtendedDigest.java new file mode 100644 index 000000000..c068b40c7 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/ExtendedDigest.java @@ -0,0 +1,13 @@ +package com.google.bitcoin.bouncycastle.crypto; + +public interface ExtendedDigest + extends Digest +{ + /** + * Return the size in bytes of the internal buffer the digest applies it's compression + * function to. + * + * @return byte length of the digests internal buffer. + */ + public int getByteLength(); +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/InvalidCipherTextException.java b/src/com/google/bitcoin/bouncycastle/crypto/InvalidCipherTextException.java new file mode 100644 index 000000000..5f24fddcd --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/InvalidCipherTextException.java @@ -0,0 +1,27 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * this exception is thrown whenever we find something we don't expect in a + * message. + */ +public class InvalidCipherTextException + extends CryptoException +{ + /** + * base constructor. + */ + public InvalidCipherTextException() + { + } + + /** + * create a InvalidCipherTextException with the given message. + * + * @param message the message to be carried with the exception. + */ + public InvalidCipherTextException( + String message) + { + super(message); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/KeyGenerationParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/KeyGenerationParameters.java new file mode 100644 index 000000000..76066cb9c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/KeyGenerationParameters.java @@ -0,0 +1,48 @@ +package com.google.bitcoin.bouncycastle.crypto; + +import java.security.SecureRandom; + +/** + * The base class for parameters to key generators. + */ +public class KeyGenerationParameters +{ + private SecureRandom random; + private int strength; + + /** + * initialise the generator with a source of randomness + * and a strength (in bits). + * + * @param random the random byte source. + * @param strength the size, in bits, of the keys we want to produce. + */ + public KeyGenerationParameters( + SecureRandom random, + int strength) + { + this.random = random; + this.strength = strength; + } + + /** + * return the random source associated with this + * generator. + * + * @return the generators random source. + */ + public SecureRandom getRandom() + { + return random; + } + + /** + * return the bit strength for keys produced by this generator, + * + * @return the strength of the keys this generator produces (in bits). + */ + public int getStrength() + { + return strength; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/Mac.java b/src/com/google/bitcoin/bouncycastle/crypto/Mac.java new file mode 100644 index 000000000..e968586bd --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/Mac.java @@ -0,0 +1,71 @@ +package com.google.bitcoin.bouncycastle.crypto; + + +/** + * The base interface for implementations of message authentication codes (MACs). + */ +public interface Mac +{ + /** + * Initialise the MAC. + * + * @param params the key and other data required by the MAC. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init(CipherParameters params) + throws IllegalArgumentException; + + /** + * Return the name of the algorithm the MAC implements. + * + * @return the name of the algorithm the MAC implements. + */ + public String getAlgorithmName(); + + /** + * Return the block size for this MAC (in bytes). + * + * @return the block size for this MAC in bytes. + */ + public int getMacSize(); + + /** + * add a single byte to the mac for processing. + * + * @param in the byte to be processed. + * @exception IllegalStateException if the MAC is not initialised. + */ + public void update(byte in) + throws IllegalStateException; + + /** + * @param in the array containing the input. + * @param inOff the index in the array the data begins at. + * @param len the length of the input starting at inOff. + * @exception IllegalStateException if the MAC is not initialised. + * @exception DataLengthException if there isn't enough data in in. + */ + public void update(byte[] in, int inOff, int len) + throws DataLengthException, IllegalStateException; + + /** + * Compute the final stage of the MAC writing the output to the out + * parameter. + *

+ * doFinal leaves the MAC in the same state it was after the last init. + * + * @param out the array the MAC is to be output to. + * @param outOff the offset into the out buffer the output is to start at. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the MAC is not initialised. + */ + public int doFinal(byte[] out, int outOff) + throws DataLengthException, IllegalStateException; + + /** + * Reset the MAC. At the end of resetting the MAC should be in the + * in the same state it was after the last init (if there was one). + */ + public void reset(); +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/MaxBytesExceededException.java b/src/com/google/bitcoin/bouncycastle/crypto/MaxBytesExceededException.java new file mode 100644 index 000000000..c403a514e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/MaxBytesExceededException.java @@ -0,0 +1,27 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * this exception is thrown whenever a cipher requires a change of key, iv + * or similar after x amount of bytes enciphered + */ +public class MaxBytesExceededException + extends RuntimeCryptoException +{ + /** + * base constructor. + */ + public MaxBytesExceededException() + { + } + + /** + * create an with the given message. + * + * @param message the message to be carried with the exception. + */ + public MaxBytesExceededException( + String message) + { + super(message); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/PBEParametersGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/PBEParametersGenerator.java new file mode 100644 index 000000000..25285c5ce --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/PBEParametersGenerator.java @@ -0,0 +1,157 @@ +package com.google.bitcoin.bouncycastle.crypto; + +import com.google.bitcoin.bouncycastle.util.Strings; + +/** + * super class for all Password Based Encryption (PBE) parameter generator classes. + */ +public abstract class PBEParametersGenerator +{ + protected byte[] password; + protected byte[] salt; + protected int iterationCount; + + /** + * base constructor. + */ + protected PBEParametersGenerator() + { + } + + /** + * initialise the PBE generator. + * + * @param password the password converted into bytes (see below). + * @param salt the salt to be mixed with the password. + * @param iterationCount the number of iterations the "mixing" function + * is to be applied for. + */ + public void init( + byte[] password, + byte[] salt, + int iterationCount) + { + this.password = password; + this.salt = salt; + this.iterationCount = iterationCount; + } + + /** + * return the password byte array. + * + * @return the password byte array. + */ + public byte[] getPassword() + { + return password; + } + + /** + * return the salt byte array. + * + * @return the salt byte array. + */ + public byte[] getSalt() + { + return salt; + } + + /** + * return the iteration count. + * + * @return the iteration count. + */ + public int getIterationCount() + { + return iterationCount; + } + + /** + * generate derived parameters for a key of length keySize. + * + * @param keySize the length, in bits, of the key required. + * @return a parameters object representing a key. + */ + public abstract CipherParameters generateDerivedParameters(int keySize); + + /** + * generate derived parameters for a key of length keySize, and + * an initialisation vector (IV) of length ivSize. + * + * @param keySize the length, in bits, of the key required. + * @param ivSize the length, in bits, of the iv required. + * @return a parameters object representing a key and an IV. + */ + public abstract CipherParameters generateDerivedParameters(int keySize, int ivSize); + + /** + * generate derived parameters for a key of length keySize, specifically + * for use with a MAC. + * + * @param keySize the length, in bits, of the key required. + * @return a parameters object representing a key. + */ + public abstract CipherParameters generateDerivedMacParameters(int keySize); + + /** + * converts a password to a byte array according to the scheme in + * PKCS5 (ascii, no padding) + * + * @param password a character array reqpresenting the password. + * @return a byte array representing the password. + */ + public static byte[] PKCS5PasswordToBytes( + char[] password) + { + byte[] bytes = new byte[password.length]; + + for (int i = 0; i != bytes.length; i++) + { + bytes[i] = (byte)password[i]; + } + + return bytes; + } + + /** + * converts a password to a byte array according to the scheme in + * PKCS5 (UTF-8, no padding) + * + * @param password a character array reqpresenting the password. + * @return a byte array representing the password. + */ + public static byte[] PKCS5PasswordToUTF8Bytes( + char[] password) + { + return Strings.toUTF8ByteArray(password); + } + + /** + * converts a password to a byte array according to the scheme in + * PKCS12 (unicode, big endian, 2 zero pad bytes at the end). + * + * @param password a character array representing the password. + * @return a byte array representing the password. + */ + public static byte[] PKCS12PasswordToBytes( + char[] password) + { + if (password.length > 0) + { + // +1 for extra 2 pad bytes. + byte[] bytes = new byte[(password.length + 1) * 2]; + + for (int i = 0; i != password.length; i ++) + { + bytes[i * 2] = (byte)(password[i] >>> 8); + bytes[i * 2 + 1] = (byte)password[i]; + } + + return bytes; + } + else + { + return new byte[0]; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/RuntimeCryptoException.java b/src/com/google/bitcoin/bouncycastle/crypto/RuntimeCryptoException.java new file mode 100644 index 000000000..a49eac238 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/RuntimeCryptoException.java @@ -0,0 +1,26 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * the foundation class for the exceptions thrown by the crypto packages. + */ +public class RuntimeCryptoException + extends RuntimeException +{ + /** + * base constructor. + */ + public RuntimeCryptoException() + { + } + + /** + * create a RuntimeCryptoException with the given message. + * + * @param message the message to be carried with the exception. + */ + public RuntimeCryptoException( + String message) + { + super(message); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/Signer.java b/src/com/google/bitcoin/bouncycastle/crypto/Signer.java new file mode 100644 index 000000000..f5533e13f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/Signer.java @@ -0,0 +1,43 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * Generic signer interface for hash based and message recovery signers. + */ +public interface Signer +{ + /** + * Initialise the signer for signing or verification. + * + * @param forSigning true if for signing, false otherwise + * @param param necessary parameters. + */ + public void init(boolean forSigning, CipherParameters param); + + /** + * update the internal digest with the byte b + */ + public void update(byte b); + + /** + * update the internal digest with the byte array in + */ + public void update(byte[] in, int off, int len); + + /** + * generate a signature for the message we've been loaded with using + * the key we were initialised with. + */ + public byte[] generateSignature() + throws CryptoException, DataLengthException; + + /** + * return true if the internal state represents the signature described + * in the passed in array. + */ + public boolean verifySignature(byte[] signature); + + /** + * reset the internal state + */ + public void reset(); +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/SignerWithRecovery.java b/src/com/google/bitcoin/bouncycastle/crypto/SignerWithRecovery.java new file mode 100644 index 000000000..dee856aa7 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/SignerWithRecovery.java @@ -0,0 +1,23 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * Signer with message recovery. + */ +public interface SignerWithRecovery + extends Signer +{ + /** + * Returns true if the signer has recovered the full message as + * part of signature verification. + * + * @return true if full message recovered. + */ + public boolean hasFullMessage(); + + /** + * Returns a reference to what message was recovered (if any). + * + * @return full/partial message, null if nothing. + */ + public byte[] getRecoveredMessage(); +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/StreamBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/StreamBlockCipher.java new file mode 100644 index 000000000..e0a3eedd5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/StreamBlockCipher.java @@ -0,0 +1,108 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * a wrapper for block ciphers with a single byte block size, so that they + * can be treated like stream ciphers. + */ +public class StreamBlockCipher + implements StreamCipher +{ + private BlockCipher cipher; + + private byte[] oneByte = new byte[1]; + + /** + * basic constructor. + * + * @param cipher the block cipher to be wrapped. + * @exception IllegalArgumentException if the cipher has a block size other than + * one. + */ + public StreamBlockCipher( + BlockCipher cipher) + { + if (cipher.getBlockSize() != 1) + { + throw new IllegalArgumentException("block cipher block size != 1."); + } + + this.cipher = cipher; + } + + /** + * initialise the underlying cipher. + * + * @param forEncryption true if we are setting up for encryption, false otherwise. + * @param params the necessary parameters for the underlying cipher to be initialised. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + cipher.init(forEncryption, params); + } + + /** + * return the name of the algorithm we are wrapping. + * + * @return the name of the algorithm we are wrapping. + */ + public String getAlgorithmName() + { + return cipher.getAlgorithmName(); + } + + /** + * encrypt/decrypt a single byte returning the result. + * + * @param in the byte to be processed. + * @return the result of processing the input byte. + */ + public byte returnByte( + byte in) + { + oneByte[0] = in; + + cipher.processBlock(oneByte, 0, oneByte, 0); + + return oneByte[0]; + } + + /** + * process a block of bytes from in putting the result into out. + * + * @param in the input byte array. + * @param inOff the offset into the in array where the data to be processed starts. + * @param len the number of bytes to be processed. + * @param out the output buffer the processed bytes go into. + * @param outOff the offset into the output byte array the processed data stars at. + * @exception DataLengthException if the output buffer is too small. + */ + public void processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + throws DataLengthException + { + if (outOff + len > out.length) + { + throw new DataLengthException("output buffer too small in processBytes()"); + } + + for (int i = 0; i != len; i++) + { + cipher.processBlock(in, inOff + i, out, outOff + i); + } + } + + /** + * reset the underlying cipher. This leaves it in the same state + * it was at after the last init (if there was one). + */ + public void reset() + { + cipher.reset(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/StreamCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/StreamCipher.java new file mode 100644 index 000000000..ab092a5d0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/StreamCipher.java @@ -0,0 +1,53 @@ +package com.google.bitcoin.bouncycastle.crypto; + +/** + * the interface stream ciphers conform to. + */ +public interface StreamCipher +{ + /** + * Initialise the cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException; + + /** + * Return the name of the algorithm the cipher implements. + * + * @return the name of the algorithm the cipher implements. + */ + public String getAlgorithmName(); + + /** + * encrypt/decrypt a single byte returning the result. + * + * @param in the byte to be processed. + * @return the result of processing the input byte. + */ + public byte returnByte(byte in); + + /** + * process a block of bytes from in putting the result into out. + * + * @param in the input byte array. + * @param inOff the offset into the in array where the data to be processed starts. + * @param len the number of bytes to be processed. + * @param out the output buffer the processed bytes go into. + * @param outOff the offset into the output byte array the processed data starts at. + * @exception DataLengthException if the output buffer is too small. + */ + public void processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) + throws DataLengthException; + + /** + * reset the cipher. This leaves it in the same state + * it was at after the last init (if there was one). + */ + public void reset(); +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/Wrapper.java b/src/com/google/bitcoin/bouncycastle/crypto/Wrapper.java new file mode 100644 index 000000000..0654b0e22 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/Wrapper.java @@ -0,0 +1,18 @@ +package com.google.bitcoin.bouncycastle.crypto; + +public interface Wrapper +{ + public void init(boolean forWrapping, CipherParameters param); + + /** + * Return the name of the algorithm the wrapper implements. + * + * @return the name of the algorithm the wrapper implements. + */ + public String getAlgorithmName(); + + public byte[] wrap(byte[] in, int inOff, int inLen); + + public byte[] unwrap(byte[] in, int inOff, int inLen) + throws InvalidCipherTextException; +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/agreement/DHAgreement.java b/src/com/google/bitcoin/bouncycastle/crypto/agreement/DHAgreement.java new file mode 100644 index 000000000..128b4d60c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/agreement/DHAgreement.java @@ -0,0 +1,94 @@ +package com.google.bitcoin.bouncycastle.crypto.agreement; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPair; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.generators.DHKeyPairGenerator; +import com.google.bitcoin.bouncycastle.crypto.params.AsymmetricKeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.DHKeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHPublicKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; + +/** + * a Diffie-Hellman key exchange engine. + *

+ * note: This uses MTI/A0 key agreement in order to make the key agreement + * secure against passive attacks. If you're doing Diffie-Hellman and both + * parties have long term public keys you should look at using this. For + * further information have a look at RFC 2631. + *

+ * It's possible to extend this to more than two parties as well, for the moment + * that is left as an exercise for the reader. + */ +public class DHAgreement +{ + private DHPrivateKeyParameters key; + private DHParameters dhParams; + private BigInteger privateValue; + private SecureRandom random; + + public void init( + CipherParameters param) + { + AsymmetricKeyParameter kParam; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + kParam = (AsymmetricKeyParameter)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + kParam = (AsymmetricKeyParameter)param; + } + + + if (!(kParam instanceof DHPrivateKeyParameters)) + { + throw new IllegalArgumentException("DHEngine expects DHPrivateKeyParameters"); + } + + this.key = (DHPrivateKeyParameters)kParam; + this.dhParams = key.getParameters(); + } + + /** + * calculate our initial message. + */ + public BigInteger calculateMessage() + { + DHKeyPairGenerator dhGen = new DHKeyPairGenerator(); + dhGen.init(new DHKeyGenerationParameters(random, dhParams)); + AsymmetricCipherKeyPair dhPair = dhGen.generateKeyPair(); + + this.privateValue = ((DHPrivateKeyParameters)dhPair.getPrivate()).getX(); + + return ((DHPublicKeyParameters)dhPair.getPublic()).getY(); + } + + /** + * given a message from a given party and the corresponding public key, + * calculate the next message in the agreement sequence. In this case + * this will represent the shared secret. + */ + public BigInteger calculateAgreement( + DHPublicKeyParameters pub, + BigInteger message) + { + if (!pub.getParameters().equals(dhParams)) + { + throw new IllegalArgumentException("Diffie-Hellman public key has wrong parameters."); + } + + BigInteger p = dhParams.getP(); + + return message.modPow(key.getX(), p).multiply(pub.getY().modPow(privateValue, p)).mod(p); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/agreement/DHBasicAgreement.java b/src/com/google/bitcoin/bouncycastle/crypto/agreement/DHBasicAgreement.java new file mode 100644 index 000000000..eb0503fc0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/agreement/DHBasicAgreement.java @@ -0,0 +1,66 @@ +package com.google.bitcoin.bouncycastle.crypto.agreement; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.crypto.BasicAgreement; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.params.AsymmetricKeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.DHParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHPublicKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; + +/** + * a Diffie-Hellman key agreement class. + *

+ * note: This is only the basic algorithm, it doesn't take advantage of + * long term public keys if they are available. See the DHAgreement class + * for a "better" implementation. + */ +public class DHBasicAgreement + implements BasicAgreement +{ + private DHPrivateKeyParameters key; + private DHParameters dhParams; + + public void init( + CipherParameters param) + { + AsymmetricKeyParameter kParam; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + kParam = (AsymmetricKeyParameter)rParam.getParameters(); + } + else + { + kParam = (AsymmetricKeyParameter)param; + } + + if (!(kParam instanceof DHPrivateKeyParameters)) + { + throw new IllegalArgumentException("DHEngine expects DHPrivateKeyParameters"); + } + + this.key = (DHPrivateKeyParameters)kParam; + this.dhParams = key.getParameters(); + } + + /** + * given a short term public key from a given party calculate the next + * message in the agreement sequence. + */ + public BigInteger calculateAgreement( + CipherParameters pubKey) + { + DHPublicKeyParameters pub = (DHPublicKeyParameters)pubKey; + + if (!pub.getParameters().equals(dhParams)) + { + throw new IllegalArgumentException("Diffie-Hellman public key has wrong parameters."); + } + + return pub.getY().modPow(key.getX(), dhParams.getP()); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/agreement/ECDHBasicAgreement.java b/src/com/google/bitcoin/bouncycastle/crypto/agreement/ECDHBasicAgreement.java new file mode 100644 index 000000000..4bc33b3f8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/agreement/ECDHBasicAgreement.java @@ -0,0 +1,47 @@ +package com.google.bitcoin.bouncycastle.crypto.agreement; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.crypto.BasicAgreement; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPublicKeyParameters; +import com.google.bitcoin.bouncycastle.math.ec.ECPoint; + + +/** + * P1363 7.2.1 ECSVDP-DH + * + * ECSVDP-DH is Elliptic Curve Secret Value Derivation Primitive, + * Diffie-Hellman version. It is based on the work of [DH76], [Mil86], + * and [Kob87]. This primitive derives a shared secret value from one + * party's private key and another party's public key, where both have + * the same set of EC domain parameters. If two parties correctly + * execute this primitive, they will produce the same output. This + * primitive can be invoked by a scheme to derive a shared secret key; + * specifically, it may be used with the schemes ECKAS-DH1 and + * DL/ECKAS-DH2. It assumes that the input keys are valid (see also + * Section 7.2.2). + */ +public class ECDHBasicAgreement + implements BasicAgreement +{ + private ECPrivateKeyParameters key; + + public void init( + CipherParameters key) + { + this.key = (ECPrivateKeyParameters)key; + } + + public BigInteger calculateAgreement( + CipherParameters pubKey) + { + ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey; + ECPoint P = pub.getQ().multiply(key.getD()); + + // if (p.isInfinity()) throw new RuntimeException("d*Q == infinity"); + + return P.getX().toBigInteger(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/agreement/ECDHCBasicAgreement.java b/src/com/google/bitcoin/bouncycastle/crypto/agreement/ECDHCBasicAgreement.java new file mode 100644 index 000000000..49b554149 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/agreement/ECDHCBasicAgreement.java @@ -0,0 +1,54 @@ +package com.google.bitcoin.bouncycastle.crypto.agreement; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.crypto.BasicAgreement; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECDomainParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPublicKeyParameters; +import com.google.bitcoin.bouncycastle.math.ec.ECPoint; + + +/** + * P1363 7.2.2 ECSVDP-DHC + * + * ECSVDP-DHC is Elliptic Curve Secret Value Derivation Primitive, + * Diffie-Hellman version with cofactor multiplication. It is based on + * the work of [DH76], [Mil86], [Kob87], [LMQ98] and [Kal98a]. This + * primitive derives a shared secret value from one party's private key + * and another party's public key, where both have the same set of EC + * domain parameters. If two parties correctly execute this primitive, + * they will produce the same output. This primitive can be invoked by a + * scheme to derive a shared secret key; specifically, it may be used + * with the schemes ECKAS-DH1 and DL/ECKAS-DH2. It does not assume the + * validity of the input public key (see also Section 7.2.1). + *

+ * Note: As stated P1363 compatibility mode with ECDH can be preset, and + * in this case the implementation doesn't have a ECDH compatibility mode + * (if you want that just use ECDHBasicAgreement and note they both implement + * BasicAgreement!). + */ +public class ECDHCBasicAgreement + implements BasicAgreement +{ + ECPrivateKeyParameters key; + + public void init( + CipherParameters key) + { + this.key = (ECPrivateKeyParameters)key; + } + + public BigInteger calculateAgreement( + CipherParameters pubKey) + { + ECPublicKeyParameters pub = (ECPublicKeyParameters)pubKey; + ECDomainParameters params = pub.getParameters(); + ECPoint P = pub.getQ().multiply(params.getH().multiply(key.getD())); + + // if (p.isInfinity()) throw new RuntimeException("Invalid public key"); + + return P.getX().toBigInteger(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/agreement/ECMQVBasicAgreement.java b/src/com/google/bitcoin/bouncycastle/crypto/agreement/ECMQVBasicAgreement.java new file mode 100644 index 000000000..92012bacb --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/agreement/ECMQVBasicAgreement.java @@ -0,0 +1,86 @@ +package com.google.bitcoin.bouncycastle.crypto.agreement; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.crypto.BasicAgreement; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECDomainParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPublicKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.MQVPrivateParameters; +import com.google.bitcoin.bouncycastle.crypto.params.MQVPublicParameters; +import com.google.bitcoin.bouncycastle.math.ec.ECAlgorithms; +import com.google.bitcoin.bouncycastle.math.ec.ECConstants; +import com.google.bitcoin.bouncycastle.math.ec.ECPoint; + +public class ECMQVBasicAgreement + implements BasicAgreement +{ + MQVPrivateParameters privParams; + + public void init( + CipherParameters key) + { + this.privParams = (MQVPrivateParameters)key; + } + + public BigInteger calculateAgreement(CipherParameters pubKey) + { + MQVPublicParameters pubParams = (MQVPublicParameters)pubKey; + + ECPrivateKeyParameters staticPrivateKey = privParams.getStaticPrivateKey(); + + ECPoint agreement = calculateMqvAgreement(staticPrivateKey.getParameters(), staticPrivateKey, + privParams.getEphemeralPrivateKey(), privParams.getEphemeralPublicKey(), + pubParams.getStaticPublicKey(), pubParams.getEphemeralPublicKey()); + + return agreement.getX().toBigInteger(); + } + + // The ECMQV Primitive as described in SEC-1, 3.4 + private ECPoint calculateMqvAgreement( + ECDomainParameters parameters, + ECPrivateKeyParameters d1U, + ECPrivateKeyParameters d2U, + ECPublicKeyParameters Q2U, + ECPublicKeyParameters Q1V, + ECPublicKeyParameters Q2V) + { + BigInteger n = parameters.getN(); + int e = (n.bitLength() + 1) / 2; + BigInteger powE = ECConstants.ONE.shiftLeft(e); + + // The Q2U public key is optional + ECPoint q; + if (Q2U == null) + { + q = parameters.getG().multiply(d2U.getD()); + } + else + { + q = Q2U.getQ(); + } + + BigInteger x = q.getX().toBigInteger(); + BigInteger xBar = x.mod(powE); + BigInteger Q2UBar = xBar.setBit(e); + BigInteger s = d1U.getD().multiply(Q2UBar).mod(n).add(d2U.getD()).mod(n); + + BigInteger xPrime = Q2V.getQ().getX().toBigInteger(); + BigInteger xPrimeBar = xPrime.mod(powE); + BigInteger Q2VBar = xPrimeBar.setBit(e); + + BigInteger hs = parameters.getH().multiply(s).mod(n); + +// ECPoint p = Q1V.getQ().multiply(Q2VBar).add(Q2V.getQ()).multiply(hs); + ECPoint p = ECAlgorithms.sumOfTwoMultiplies( + Q1V.getQ(), Q2VBar.multiply(hs).mod(n), Q2V.getQ(), hs); + + if (p.isInfinity()) + { + throw new IllegalStateException("Infinity is not a valid agreement value for MQV"); + } + + return p; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/agreement/kdf/DHKDFParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/agreement/kdf/DHKDFParameters.java new file mode 100644 index 000000000..af856cde7 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/agreement/kdf/DHKDFParameters.java @@ -0,0 +1,56 @@ +package com.google.bitcoin.bouncycastle.crypto.agreement.kdf; + +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.crypto.DerivationParameters; + +public class DHKDFParameters + implements DerivationParameters +{ + private final DERObjectIdentifier algorithm; + private final int keySize; + private final byte[] z; + private final byte[] extraInfo; + + public DHKDFParameters( + DERObjectIdentifier algorithm, + int keySize, + byte[] z) + { + this.algorithm = algorithm; + this.keySize = keySize; + this.z = z; + this.extraInfo = null; + } + + public DHKDFParameters( + DERObjectIdentifier algorithm, + int keySize, + byte[] z, + byte[] extraInfo) + { + this.algorithm = algorithm; + this.keySize = keySize; + this.z = z; + this.extraInfo = extraInfo; + } + + public DERObjectIdentifier getAlgorithm() + { + return algorithm; + } + + public int getKeySize() + { + return keySize; + } + + public byte[] getZ() + { + return z; + } + + public byte[] getExtraInfo() + { + return extraInfo; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/agreement/kdf/DHKEKGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/agreement/kdf/DHKEKGenerator.java new file mode 100644 index 000000000..e3a95ef38 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/agreement/kdf/DHKEKGenerator.java @@ -0,0 +1,132 @@ +package com.google.bitcoin.bouncycastle.crypto.agreement.kdf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.DerivationFunction; +import com.google.bitcoin.bouncycastle.crypto.DerivationParameters; +import com.google.bitcoin.bouncycastle.crypto.Digest; + +/** + * RFC 2631 Diffie-hellman KEK derivation function. + */ +public class DHKEKGenerator + implements DerivationFunction +{ + private final Digest digest; + + private DERObjectIdentifier algorithm; + private int keySize; + private byte[] z; + private byte[] partyAInfo; + + public DHKEKGenerator( + Digest digest) + { + this.digest = digest; + } + + public void init(DerivationParameters param) + { + DHKDFParameters params = (DHKDFParameters)param; + + this.algorithm = params.getAlgorithm(); + this.keySize = params.getKeySize(); + this.z = params.getZ(); + this.partyAInfo = params.getExtraInfo(); + } + + public Digest getDigest() + { + return digest; + } + + public int generateBytes(byte[] out, int outOff, int len) + throws DataLengthException, IllegalArgumentException + { + if ((out.length - len) < outOff) + { + throw new DataLengthException("output buffer too small"); + } + + long oBytes = len; + int outLen = digest.getDigestSize(); + + // + // this is at odds with the standard implementation, the + // maximum value should be hBits * (2^32 - 1) where hBits + // is the digest output size in bits. We can't have an + // array with a long index at the moment... + // + if (oBytes > ((2L << 32) - 1)) + { + throw new IllegalArgumentException("Output length too large"); + } + + int cThreshold = (int)((oBytes + outLen - 1) / outLen); + + byte[] dig = new byte[digest.getDigestSize()]; + + int counter = 1; + + for (int i = 0; i < cThreshold; i++) + { + digest.update(z, 0, z.length); + + // OtherInfo + ASN1EncodableVector v1 = new ASN1EncodableVector(); + // KeySpecificInfo + ASN1EncodableVector v2 = new ASN1EncodableVector(); + + v2.add(algorithm); + v2.add(new DEROctetString(integerToBytes(counter))); + + v1.add(new DERSequence(v2)); + + if (partyAInfo != null) + { + v1.add(new DERTaggedObject(true, 0, new DEROctetString(partyAInfo))); + } + + v1.add(new DERTaggedObject(true, 2, new DEROctetString(integerToBytes(keySize)))); + + byte[] other = new DERSequence(v1).getDEREncoded(); + + digest.update(other, 0, other.length); + + digest.doFinal(dig, 0); + + if (len > outLen) + { + System.arraycopy(dig, 0, out, outOff, outLen); + outOff += outLen; + len -= outLen; + } + else + { + System.arraycopy(dig, 0, out, outOff, len); + } + + counter++; + } + + digest.reset(); + + return len; + } + + private byte[] integerToBytes(int keySize) + { + byte[] val = new byte[4]; + + val[0] = (byte)(keySize >> 24); + val[1] = (byte)(keySize >> 16); + val[2] = (byte)(keySize >> 8); + val[3] = (byte)keySize; + + return val; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/agreement/kdf/ECDHKEKGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/agreement/kdf/ECDHKEKGenerator.java new file mode 100644 index 000000000..d09949a27 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/agreement/kdf/ECDHKEKGenerator.java @@ -0,0 +1,74 @@ +package com.google.bitcoin.bouncycastle.crypto.agreement.kdf; + +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.DERNull; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.asn1.DERTaggedObject; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.DerivationFunction; +import com.google.bitcoin.bouncycastle.crypto.DerivationParameters; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.generators.KDF2BytesGenerator; +import com.google.bitcoin.bouncycastle.crypto.params.KDFParameters; + +/** + * X9.63 based key derivation function for ECDH CMS. + */ +public class ECDHKEKGenerator + implements DerivationFunction +{ + private DerivationFunction kdf; + + private DERObjectIdentifier algorithm; + private int keySize; + private byte[] z; + + public ECDHKEKGenerator( + Digest digest) + { + this.kdf = new KDF2BytesGenerator(digest); + } + + public void init(DerivationParameters param) + { + DHKDFParameters params = (DHKDFParameters)param; + + this.algorithm = params.getAlgorithm(); + this.keySize = params.getKeySize(); + this.z = params.getZ(); + } + + public Digest getDigest() + { + return kdf.getDigest(); + } + + public int generateBytes(byte[] out, int outOff, int len) + throws DataLengthException, IllegalArgumentException + { + // ECC-CMS-SharedInfo + ASN1EncodableVector v = new ASN1EncodableVector(); + + v.add(new AlgorithmIdentifier(algorithm, new DERNull())); + v.add(new DERTaggedObject(true, 2, new DEROctetString(integerToBytes(keySize)))); + + kdf.init(new KDFParameters(z, new DERSequence(v).getDEREncoded())); + + return kdf.generateBytes(out, outOff, len); + } + + private byte[] integerToBytes(int keySize) + { + byte[] val = new byte[4]; + + val[0] = (byte)(keySize >> 24); + val[1] = (byte)(keySize >> 16); + val[2] = (byte)(keySize >> 8); + val[3] = (byte)keySize; + + return val; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6Client.java b/src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6Client.java new file mode 100644 index 000000000..03b187681 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6Client.java @@ -0,0 +1,93 @@ +package com.google.bitcoin.bouncycastle.crypto.agreement.srp; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.CryptoException; +import com.google.bitcoin.bouncycastle.crypto.Digest; + +/** + * Implements the client side SRP-6a protocol. Note that this class is stateful, and therefore NOT threadsafe. + * This implementation of SRP is based on the optimized message sequence put forth by Thomas Wu in the paper + * "SRP-6: Improvements and Refinements to the Secure Remote Password Protocol, 2002" + */ +public class SRP6Client +{ + protected BigInteger N; + protected BigInteger g; + + protected BigInteger a; + protected BigInteger A; + + protected BigInteger B; + + protected BigInteger x; + protected BigInteger u; + protected BigInteger S; + + protected Digest digest; + protected SecureRandom random; + + public SRP6Client() + { + } + + /** + * Initialises the client to begin new authentication attempt + * @param N The safe prime associated with the client's verifier + * @param g The group parameter associated with the client's verifier + * @param digest The digest algorithm associated with the client's verifier + * @param random For key generation + */ + public void init(BigInteger N, BigInteger g, Digest digest, SecureRandom random) + { + this.N = N; + this.g = g; + this.digest = digest; + this.random = random; + } + + /** + * Generates client's credentials given the client's salt, identity and password + * @param salt The salt used in the client's verifier. + * @param identity The user's identity (eg. username) + * @param password The user's password + * @return Client's public value to send to server + */ + public BigInteger generateClientCredentials(byte[] salt, byte[] identity, byte[] password) + { + this.x = SRP6Util.calculateX(digest, N, salt, identity, password); + this.a = selectPrivateValue(); + this.A = g.modPow(a, N); + + return A; + } + + /** + * Generates client's verification message given the server's credentials + * @param serverB The server's credentials + * @return Client's verification message for the server + * @throws CryptoException If server's credentials are invalid + */ + public BigInteger calculateSecret(BigInteger serverB) throws CryptoException + { + this.B = SRP6Util.validatePublicValue(N, serverB); + this.u = SRP6Util.calculateU(digest, N, A, B); + this.S = calculateS(); + + return S; + } + + protected BigInteger selectPrivateValue() + { + return SRP6Util.generatePrivateValue(digest, N, g, random); + } + + private BigInteger calculateS() + { + BigInteger k = SRP6Util.calculateK(digest, N, g); + BigInteger exp = u.multiply(x).add(a); + BigInteger tmp = g.modPow(x, N).multiply(k).mod(N); + return B.subtract(tmp).mod(N).modPow(exp, N); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6Server.java b/src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6Server.java new file mode 100644 index 000000000..cdfa0e3b6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6Server.java @@ -0,0 +1,90 @@ +package com.google.bitcoin.bouncycastle.crypto.agreement.srp; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.CryptoException; +import com.google.bitcoin.bouncycastle.crypto.Digest; + +/** + * Implements the server side SRP-6a protocol. Note that this class is stateful, and therefore NOT threadsafe. + * This implementation of SRP is based on the optimized message sequence put forth by Thomas Wu in the paper + * "SRP-6: Improvements and Refinements to the Secure Remote Password Protocol, 2002" + */ +public class SRP6Server +{ + protected BigInteger N; + protected BigInteger g; + protected BigInteger v; + + protected SecureRandom random; + protected Digest digest; + + protected BigInteger A; + + protected BigInteger b; + protected BigInteger B; + + protected BigInteger u; + protected BigInteger S; + + public SRP6Server() + { + } + + /** + * Initialises the server to accept a new client authentication attempt + * @param N The safe prime associated with the client's verifier + * @param g The group parameter associated with the client's verifier + * @param v The client's verifier + * @param digest The digest algorithm associated with the client's verifier + * @param random For key generation + */ + public void init(BigInteger N, BigInteger g, BigInteger v, Digest digest, SecureRandom random) + { + this.N = N; + this.g = g; + this.v = v; + + this.random = random; + this.digest = digest; + } + + /** + * Generates the server's credentials that are to be sent to the client. + * @return The server's public value to the client + */ + public BigInteger generateServerCredentials() + { + BigInteger k = SRP6Util.calculateK(digest, N, g); + this.b = selectPrivateValue(); + this.B = k.multiply(v).mod(N).add(g.modPow(b, N)).mod(N); + + return B; + } + + /** + * Processes the client's credentials. If valid the shared secret is generated and returned. + * @param clientA The client's credentials + * @return A shared secret BigInteger + * @throws CryptoException If client's credentials are invalid + */ + public BigInteger calculateSecret(BigInteger clientA) throws CryptoException + { + this.A = SRP6Util.validatePublicValue(N, clientA); + this.u = SRP6Util.calculateU(digest, N, A, B); + this.S = calculateS(); + + return S; + } + + protected BigInteger selectPrivateValue() + { + return SRP6Util.generatePrivateValue(digest, N, g, random); + } + + private BigInteger calculateS() + { + return v.modPow(u, N).multiply(A).mod(N).modPow(b, N); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6Util.java b/src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6Util.java new file mode 100644 index 000000000..1747ca333 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6Util.java @@ -0,0 +1,91 @@ +package com.google.bitcoin.bouncycastle.crypto.agreement.srp; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.CryptoException; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.util.BigIntegers; + +public class SRP6Util +{ + private static BigInteger ZERO = BigInteger.valueOf(0); + private static BigInteger ONE = BigInteger.valueOf(1); + + public static BigInteger calculateK(Digest digest, BigInteger N, BigInteger g) + { + return hashPaddedPair(digest, N, N, g); + } + + public static BigInteger calculateU(Digest digest, BigInteger N, BigInteger A, BigInteger B) + { + return hashPaddedPair(digest, N, A, B); + } + + public static BigInteger calculateX(Digest digest, BigInteger N, byte[] salt, byte[] identity, byte[] password) + { + byte[] output = new byte[digest.getDigestSize()]; + + digest.update(identity, 0, identity.length); + digest.update((byte)':'); + digest.update(password, 0, password.length); + digest.doFinal(output, 0); + + digest.update(salt, 0, salt.length); + digest.update(output, 0, output.length); + digest.doFinal(output, 0); + + return new BigInteger(1, output).mod(N); + } + + public static BigInteger generatePrivateValue(Digest digest, BigInteger N, BigInteger g, SecureRandom random) + { + int minBits = Math.min(256, N.bitLength() / 2); + BigInteger min = ONE.shiftLeft(minBits - 1); + BigInteger max = N.subtract(ONE); + + return BigIntegers.createRandomInRange(min, max, random); + } + + public static BigInteger validatePublicValue(BigInteger N, BigInteger val) + throws CryptoException + { + val = val.mod(N); + + // Check that val % N != 0 + if (val.equals(ZERO)) + { + throw new CryptoException("Invalid public value: 0"); + } + + return val; + } + + private static BigInteger hashPaddedPair(Digest digest, BigInteger N, BigInteger n1, BigInteger n2) + { + int padLength = (N.bitLength() + 7) / 8; + + byte[] n1_bytes = getPadded(n1, padLength); + byte[] n2_bytes = getPadded(n2, padLength); + + digest.update(n1_bytes, 0, n1_bytes.length); + digest.update(n2_bytes, 0, n2_bytes.length); + + byte[] output = new byte[digest.getDigestSize()]; + digest.doFinal(output, 0); + + return new BigInteger(1, output).mod(N); + } + + private static byte[] getPadded(BigInteger n, int length) + { + byte[] bs = BigIntegers.asUnsignedByteArray(n); + if (bs.length < length) + { + byte[] tmp = new byte[length]; + System.arraycopy(bs, 0, tmp, length - bs.length, bs.length); + bs = tmp; + } + return bs; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6VerifierGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6VerifierGenerator.java new file mode 100644 index 000000000..1cf2ff2a6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/agreement/srp/SRP6VerifierGenerator.java @@ -0,0 +1,47 @@ +package com.google.bitcoin.bouncycastle.crypto.agreement.srp; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.crypto.Digest; + +/** + * Generates new SRP verifier for user + */ +public class SRP6VerifierGenerator +{ + protected BigInteger N; + protected BigInteger g; + protected Digest digest; + + public SRP6VerifierGenerator() + { + } + + /** + * Initialises generator to create new verifiers + * @param N The safe prime to use (see DHParametersGenerator) + * @param g The group parameter to use (see DHParametersGenerator) + * @param digest The digest to use. The same digest type will need to be used later for the actual authentication + * attempt. Also note that the final session key size is dependent on the chosen digest. + */ + public void init(BigInteger N, BigInteger g, Digest digest) + { + this.N = N; + this.g = g; + this.digest = digest; + } + + /** + * Creates a new SRP verifier + * @param salt The salt to use, generally should be large and random + * @param identity The user's identifying information (eg. username) + * @param password The user's password + * @return A new verifier for use in future SRP authentication + */ + public BigInteger generateVerifier(byte[] salt, byte[] identity, byte[] password) + { + BigInteger x = SRP6Util.calculateX(digest, N, salt, identity, password); + + return g.modPow(x, N); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/GOST3411Digest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/GOST3411Digest.java new file mode 100644 index 000000000..04d941e30 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/GOST3411Digest.java @@ -0,0 +1,344 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.ExtendedDigest; +import com.google.bitcoin.bouncycastle.crypto.engines.GOST28147Engine; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithSBox; + +/** + * implementation of GOST R 34.11-94 + */ +public class GOST3411Digest + implements ExtendedDigest +{ + private static final int DIGEST_LENGTH = 32; + + private byte[] H = new byte[32], L = new byte[32], + M = new byte[32], Sum = new byte[32]; + private byte[][] C = new byte[4][32]; + + private byte[] xBuf = new byte[32]; + private int xBufOff; + private long byteCount; + + private BlockCipher cipher = new GOST28147Engine(); + + /** + * Standard constructor + */ + public GOST3411Digest() + { + cipher.init(true, new ParametersWithSBox(null, GOST28147Engine.getSBox("D-A"))); + + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public GOST3411Digest(GOST3411Digest t) + { + cipher.init(true, new ParametersWithSBox(null, GOST28147Engine.getSBox("D-A"))); + + reset(); + + System.arraycopy(t.H, 0, this.H, 0, t.H.length); + System.arraycopy(t.L, 0, this.L, 0, t.L.length); + System.arraycopy(t.M, 0, this.M, 0, t.M.length); + System.arraycopy(t.Sum, 0, this.Sum, 0, t.Sum.length); + System.arraycopy(t.C[1], 0, this.C[1], 0, t.C[1].length); + System.arraycopy(t.C[2], 0, this.C[2], 0, t.C[2].length); + System.arraycopy(t.C[3], 0, this.C[3], 0, t.C[3].length); + System.arraycopy(t.xBuf, 0, this.xBuf, 0, t.xBuf.length); + + this.xBufOff = t.xBufOff; + this.byteCount = t.byteCount; + } + + public String getAlgorithmName() + { + return "GOST3411"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + public void update(byte in) + { + xBuf[xBufOff++] = in; + if (xBufOff == xBuf.length) + { + sumByteArray(xBuf); // calc sum M + processBlock(xBuf, 0); + xBufOff = 0; + } + byteCount++; + } + + public void update(byte[] in, int inOff, int len) + { + while ((xBufOff != 0) && (len > 0)) + { + update(in[inOff]); + inOff++; + len--; + } + + while (len > xBuf.length) + { + System.arraycopy(in, inOff, xBuf, 0, xBuf.length); + + sumByteArray(xBuf); // calc sum M + processBlock(xBuf, 0); + inOff += xBuf.length; + len -= xBuf.length; + byteCount += xBuf.length; + } + + // load in the remainder. + while (len > 0) + { + update(in[inOff]); + inOff++; + len--; + } + } + + // (i + 1 + 4(k - 1)) = 8i + k i = 0-3, k = 1-8 + private byte[] K = new byte[32]; + + private byte[] P(byte[] in) + { + for(int k = 0; k < 8; k++) + { + K[4*k] = in[k]; + K[1 + 4*k] = in[ 8 + k]; + K[2 + 4*k] = in[16 + k]; + K[3 + 4*k] = in[24 + k]; + } + + return K; + } + + //A (x) = (x0 ^ x1) || x3 || x2 || x1 + byte[] a = new byte[8]; + private byte[] A(byte[] in) + { + for(int j=0; j<8; j++) + { + a[j]=(byte)(in[j] ^ in[j+8]); + } + + System.arraycopy(in, 8, in, 0, 24); + System.arraycopy(a, 0, in, 24, 8); + + return in; + } + + //Encrypt function, ECB mode + private void E(byte[] key, byte[] s, int sOff, byte[] in, int inOff) + { + cipher.init(true, new KeyParameter(key)); + + cipher.processBlock(in, inOff, s, sOff); + } + + // (in:) n16||..||n1 ==> (out:) n1^n2^n3^n4^n13^n16||n16||..||n2 + short[] wS = new short[16], w_S = new short[16]; + + private void fw(byte[] in) + { + cpyBytesToShort(in, wS); + w_S[15] = (short)(wS[0] ^ wS[1] ^ wS[2] ^ wS[3] ^ wS[12] ^ wS[15]); + System.arraycopy(wS, 1, w_S, 0, 15); + cpyShortToBytes(w_S, in); + } + + // block processing + byte[] S = new byte[32]; + byte[] U = new byte[32], V = new byte[32], W = new byte[32]; + + protected void processBlock(byte[] in, int inOff) + { + System.arraycopy(in, inOff, M, 0, 32); + + //key step 1 + + // H = h3 || h2 || h1 || h0 + // S = s3 || s2 || s1 || s0 + System.arraycopy(H, 0, U, 0, 32); + System.arraycopy(M, 0, V, 0, 32); + for (int j=0; j<32; j++) + { + W[j] = (byte)(U[j]^V[j]); + } + // Encrypt gost28147-ECB + E(P(W), S, 0, H, 0); // s0 = EK0 [h0] + + //keys step 2,3,4 + for (int i=1; i<4; i++) + { + byte[] tmpA = A(U); + for (int j=0; j<32; j++) + { + U[j] = (byte)(tmpA[j] ^ C[i][j]); + } + V = A(A(V)); + for (int j=0; j<32; j++) + { + W[j] = (byte)(U[j]^V[j]); + } + // Encrypt gost28147-ECB + E(P(W), S, i * 8, H, i * 8); // si = EKi [hi] + } + + // x(M, H) = y61(H^y(M^y12(S))) + for(int n = 0; n < 12; n++) + { + fw(S); + } + for(int n = 0; n < 32; n++) + { + S[n] = (byte)(S[n] ^ M[n]); + } + + fw(S); + + for(int n = 0; n < 32; n++) + { + S[n] = (byte)(H[n] ^ S[n]); + } + for(int n = 0; n < 61; n++) + { + fw(S); + } + System.arraycopy(S, 0, H, 0, H.length); + } + + private void finish() + { + LongToBytes(byteCount * 8, L, 0); // get length into L (byteCount * 8 = bitCount) + + while (xBufOff != 0) + { + update((byte)0); + } + + processBlock(L, 0); + processBlock(Sum, 0); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + System.arraycopy(H, 0, out, outOff, H.length); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + private static final byte[] C2 = { + 0x00,(byte)0xFF,0x00,(byte)0xFF,0x00,(byte)0xFF,0x00,(byte)0xFF, + (byte)0xFF,0x00,(byte)0xFF,0x00,(byte)0xFF,0x00,(byte)0xFF,0x00, + 0x00,(byte)0xFF,(byte)0xFF,0x00,(byte)0xFF,0x00,0x00,(byte)0xFF, + (byte)0xFF,0x00,0x00,0x00,(byte)0xFF,(byte)0xFF,0x00,(byte)0xFF}; + + public void reset() + { + byteCount = 0; + xBufOff = 0; + + for(int i=0; i (Sum + a mod (2^256)) + private void sumByteArray(byte[] in) + { + int carry = 0; + + for (int i = 0; i != Sum.length; i++) + { + int sum = (Sum[i] & 0xff) + (in[i] & 0xff) + carry; + + Sum[i] = (byte)sum; + + carry = sum >>> 8; + } + } + + private void LongToBytes(long r, byte[] out, int outOff) + { + out[outOff + 7] = (byte)(r >> 56); + out[outOff + 6] = (byte)(r >> 48); + out[outOff + 5] = (byte)(r >> 40); + out[outOff + 4] = (byte)(r >> 32); + out[outOff + 3] = (byte)(r >> 24); + out[outOff + 2] = (byte)(r >> 16); + out[outOff + 1] = (byte)(r >> 8); + out[outOff] = (byte)r; + } + + private void cpyBytesToShort(byte[] S, short[] wS) + { + for(int i=0; i> 8); + S[i*2] = (byte)wS[i]; + } + } + + public int getByteLength() + { + return 32; + } +} + + diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/GeneralDigest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/GeneralDigest.java new file mode 100644 index 000000000..62e7d9b2d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/GeneralDigest.java @@ -0,0 +1,135 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + +import com.google.bitcoin.bouncycastle.crypto.ExtendedDigest; + +/** + * base implementation of MD4 family style digest as outlined in + * "Handbook of Applied Cryptography", pages 344 - 347. + */ +public abstract class GeneralDigest + implements ExtendedDigest +{ + private static final int BYTE_LENGTH = 64; + private byte[] xBuf; + private int xBufOff; + + private long byteCount; + + /** + * Standard constructor + */ + protected GeneralDigest() + { + xBuf = new byte[4]; + xBufOff = 0; + } + + /** + * Copy constructor. We are using copy constructors in place + * of the Object.clone() interface as this interface is not + * supported by J2ME. + */ + protected GeneralDigest(GeneralDigest t) + { + xBuf = new byte[t.xBuf.length]; + System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length); + + xBufOff = t.xBufOff; + byteCount = t.byteCount; + } + + public void update( + byte in) + { + xBuf[xBufOff++] = in; + + if (xBufOff == xBuf.length) + { + processWord(xBuf, 0); + xBufOff = 0; + } + + byteCount++; + } + + public void update( + byte[] in, + int inOff, + int len) + { + // + // fill the current word + // + while ((xBufOff != 0) && (len > 0)) + { + update(in[inOff]); + + inOff++; + len--; + } + + // + // process whole words. + // + while (len > xBuf.length) + { + processWord(in, inOff); + + inOff += xBuf.length; + len -= xBuf.length; + byteCount += xBuf.length; + } + + // + // load in the remainder. + // + while (len > 0) + { + update(in[inOff]); + + inOff++; + len--; + } + } + + public void finish() + { + long bitLength = (byteCount << 3); + + // + // add the pad bytes. + // + update((byte)128); + + while (xBufOff != 0) + { + update((byte)0); + } + + processLength(bitLength); + + processBlock(); + } + + public void reset() + { + byteCount = 0; + + xBufOff = 0; + for (int i = 0; i < xBuf.length; i++) + { + xBuf[i] = 0; + } + } + + public int getByteLength() + { + return BYTE_LENGTH; + } + + protected abstract void processWord(byte[] in, int inOff); + + protected abstract void processLength(long bitLength); + + protected abstract void processBlock(); +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/LongDigest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/LongDigest.java new file mode 100644 index 000000000..e63bbaf9d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/LongDigest.java @@ -0,0 +1,354 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + +import com.google.bitcoin.bouncycastle.crypto.ExtendedDigest; +import com.google.bitcoin.bouncycastle.crypto.util.Pack; + +/** + * Base class for SHA-384 and SHA-512. + */ +public abstract class LongDigest + implements ExtendedDigest +{ + private static final int BYTE_LENGTH = 128; + + private byte[] xBuf; + private int xBufOff; + + private long byteCount1; + private long byteCount2; + + protected long H1, H2, H3, H4, H5, H6, H7, H8; + + private long[] W = new long[80]; + private int wOff; + + /** + * Constructor for variable length word + */ + protected LongDigest() + { + xBuf = new byte[8]; + xBufOff = 0; + + reset(); + } + + /** + * Copy constructor. We are using copy constructors in place + * of the Object.clone() interface as this interface is not + * supported by J2ME. + */ + protected LongDigest(LongDigest t) + { + xBuf = new byte[t.xBuf.length]; + System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length); + + xBufOff = t.xBufOff; + byteCount1 = t.byteCount1; + byteCount2 = t.byteCount2; + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + H5 = t.H5; + H6 = t.H6; + H7 = t.H7; + H8 = t.H8; + + System.arraycopy(t.W, 0, W, 0, t.W.length); + wOff = t.wOff; + } + + public void update( + byte in) + { + xBuf[xBufOff++] = in; + + if (xBufOff == xBuf.length) + { + processWord(xBuf, 0); + xBufOff = 0; + } + + byteCount1++; + } + + public void update( + byte[] in, + int inOff, + int len) + { + // + // fill the current word + // + while ((xBufOff != 0) && (len > 0)) + { + update(in[inOff]); + + inOff++; + len--; + } + + // + // process whole words. + // + while (len > xBuf.length) + { + processWord(in, inOff); + + inOff += xBuf.length; + len -= xBuf.length; + byteCount1 += xBuf.length; + } + + // + // load in the remainder. + // + while (len > 0) + { + update(in[inOff]); + + inOff++; + len--; + } + } + + public void finish() + { + adjustByteCounts(); + + long lowBitLength = byteCount1 << 3; + long hiBitLength = byteCount2; + + // + // add the pad bytes. + // + update((byte)128); + + while (xBufOff != 0) + { + update((byte)0); + } + + processLength(lowBitLength, hiBitLength); + + processBlock(); + } + + public void reset() + { + byteCount1 = 0; + byteCount2 = 0; + + xBufOff = 0; + for (int i = 0; i < xBuf.length; i++) + { + xBuf[i] = 0; + } + + wOff = 0; + for (int i = 0; i != W.length; i++) + { + W[i] = 0; + } + } + + public int getByteLength() + { + return BYTE_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + W[wOff] = Pack.bigEndianToLong(in, inOff); + + if (++wOff == 16) + { + processBlock(); + } + } + + /** + * adjust the byte counts so that byteCount2 represents the + * upper long (less 3 bits) word of the byte count. + */ + private void adjustByteCounts() + { + if (byteCount1 > 0x1fffffffffffffffL) + { + byteCount2 += (byteCount1 >>> 61); + byteCount1 &= 0x1fffffffffffffffL; + } + } + + protected void processLength( + long lowW, + long hiW) + { + if (wOff > 14) + { + processBlock(); + } + + W[14] = hiW; + W[15] = lowW; + } + + protected void processBlock() + { + adjustByteCounts(); + + // + // expand 16 word block into 80 word blocks. + // + for (int t = 16; t <= 79; t++) + { + W[t] = Sigma1(W[t - 2]) + W[t - 7] + Sigma0(W[t - 15]) + W[t - 16]; + } + + // + // set up working variables. + // + long a = H1; + long b = H2; + long c = H3; + long d = H4; + long e = H5; + long f = H6; + long g = H7; + long h = H8; + + int t = 0; + for(int i = 0; i < 10; i ++) + { + // t = 8 * i + h += Sum1(e) + Ch(e, f, g) + K[t] + W[t++]; + d += h; + h += Sum0(a) + Maj(a, b, c); + + // t = 8 * i + 1 + g += Sum1(d) + Ch(d, e, f) + K[t] + W[t++]; + c += g; + g += Sum0(h) + Maj(h, a, b); + + // t = 8 * i + 2 + f += Sum1(c) + Ch(c, d, e) + K[t] + W[t++]; + b += f; + f += Sum0(g) + Maj(g, h, a); + + // t = 8 * i + 3 + e += Sum1(b) + Ch(b, c, d) + K[t] + W[t++]; + a += e; + e += Sum0(f) + Maj(f, g, h); + + // t = 8 * i + 4 + d += Sum1(a) + Ch(a, b, c) + K[t] + W[t++]; + h += d; + d += Sum0(e) + Maj(e, f, g); + + // t = 8 * i + 5 + c += Sum1(h) + Ch(h, a, b) + K[t] + W[t++]; + g += c; + c += Sum0(d) + Maj(d, e, f); + + // t = 8 * i + 6 + b += Sum1(g) + Ch(g, h, a) + K[t] + W[t++]; + f += b; + b += Sum0(c) + Maj(c, d, e); + + // t = 8 * i + 7 + a += Sum1(f) + Ch(f, g, h) + K[t] + W[t++]; + e += a; + a += Sum0(b) + Maj(b, c, d); + } + + H1 += a; + H2 += b; + H3 += c; + H4 += d; + H5 += e; + H6 += f; + H7 += g; + H8 += h; + + // + // reset the offset and clean out the word buffer. + // + wOff = 0; + for (int i = 0; i < 16; i++) + { + W[i] = 0; + } + } + + /* SHA-384 and SHA-512 functions (as for SHA-256 but for longs) */ + private long Ch( + long x, + long y, + long z) + { + return ((x & y) ^ ((~x) & z)); + } + + private long Maj( + long x, + long y, + long z) + { + return ((x & y) ^ (x & z) ^ (y & z)); + } + + private long Sum0( + long x) + { + return ((x << 36)|(x >>> 28)) ^ ((x << 30)|(x >>> 34)) ^ ((x << 25)|(x >>> 39)); + } + + private long Sum1( + long x) + { + return ((x << 50)|(x >>> 14)) ^ ((x << 46)|(x >>> 18)) ^ ((x << 23)|(x >>> 41)); + } + + private long Sigma0( + long x) + { + return ((x << 63)|(x >>> 1)) ^ ((x << 56)|(x >>> 8)) ^ (x >>> 7); + } + + private long Sigma1( + long x) + { + return ((x << 45)|(x >>> 19)) ^ ((x << 3)|(x >>> 61)) ^ (x >>> 6); + } + + /* SHA-384 and SHA-512 Constants + * (represent the first 64 bits of the fractional parts of the + * cube roots of the first sixty-four prime numbers) + */ + static final long K[] = { +0x428a2f98d728ae22L, 0x7137449123ef65cdL, 0xb5c0fbcfec4d3b2fL, 0xe9b5dba58189dbbcL, +0x3956c25bf348b538L, 0x59f111f1b605d019L, 0x923f82a4af194f9bL, 0xab1c5ed5da6d8118L, +0xd807aa98a3030242L, 0x12835b0145706fbeL, 0x243185be4ee4b28cL, 0x550c7dc3d5ffb4e2L, +0x72be5d74f27b896fL, 0x80deb1fe3b1696b1L, 0x9bdc06a725c71235L, 0xc19bf174cf692694L, +0xe49b69c19ef14ad2L, 0xefbe4786384f25e3L, 0x0fc19dc68b8cd5b5L, 0x240ca1cc77ac9c65L, +0x2de92c6f592b0275L, 0x4a7484aa6ea6e483L, 0x5cb0a9dcbd41fbd4L, 0x76f988da831153b5L, +0x983e5152ee66dfabL, 0xa831c66d2db43210L, 0xb00327c898fb213fL, 0xbf597fc7beef0ee4L, +0xc6e00bf33da88fc2L, 0xd5a79147930aa725L, 0x06ca6351e003826fL, 0x142929670a0e6e70L, +0x27b70a8546d22ffcL, 0x2e1b21385c26c926L, 0x4d2c6dfc5ac42aedL, 0x53380d139d95b3dfL, +0x650a73548baf63deL, 0x766a0abb3c77b2a8L, 0x81c2c92e47edaee6L, 0x92722c851482353bL, +0xa2bfe8a14cf10364L, 0xa81a664bbc423001L, 0xc24b8b70d0f89791L, 0xc76c51a30654be30L, +0xd192e819d6ef5218L, 0xd69906245565a910L, 0xf40e35855771202aL, 0x106aa07032bbd1b8L, +0x19a4c116b8d2d0c8L, 0x1e376c085141ab53L, 0x2748774cdf8eeb99L, 0x34b0bcb5e19b48a8L, +0x391c0cb3c5c95a63L, 0x4ed8aa4ae3418acbL, 0x5b9cca4f7763e373L, 0x682e6ff3d6b2b8a3L, +0x748f82ee5defb2fcL, 0x78a5636f43172f60L, 0x84c87814a1f0ab72L, 0x8cc702081a6439ecL, +0x90befffa23631e28L, 0xa4506cebde82bde9L, 0xbef9a3f7b2c67915L, 0xc67178f2e372532bL, +0xca273eceea26619cL, 0xd186b8c721c0c207L, 0xeada7dd6cde0eb1eL, 0xf57d4f7fee6ed178L, +0x06f067aa72176fbaL, 0x0a637dc5a2c898a6L, 0x113f9804bef90daeL, 0x1b710b35131c471bL, +0x28db77f523047d84L, 0x32caab7b40c72493L, 0x3c9ebe0a15c9bebcL, 0x431d67c49c100d4cL, +0x4cc5d4becb3e42b6L, 0x597f299cfc657e2aL, 0x5fcb6fab3ad6faecL, 0x6c44198c4a475817L + }; +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/MD2Digest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/MD2Digest.java new file mode 100644 index 000000000..02c169f42 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/MD2Digest.java @@ -0,0 +1,237 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + +import com.google.bitcoin.bouncycastle.crypto.*; +/** + * implementation of MD2 + * as outlined in RFC1319 by B.Kaliski from RSA Laboratories April 1992 + */ +public class MD2Digest + implements ExtendedDigest +{ + private static final int DIGEST_LENGTH = 16; + + /* X buffer */ + private byte[] X = new byte[48]; + private int xOff; + /* M buffer */ + private byte[] M = new byte[16]; + private int mOff; + /* check sum */ + private byte[] C = new byte[16]; + private int COff; + + public MD2Digest() + { + reset(); + } + public MD2Digest(MD2Digest t) + { + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + System.arraycopy(t.M, 0, M, 0, t.M.length); + mOff = t.mOff; + System.arraycopy(t.C, 0, C, 0, t.C.length); + COff = t.COff; + } + /** + * return the algorithm name + * + * @return the algorithm name + */ + public String getAlgorithmName() + { + return "MD2"; + } + /** + * return the size, in bytes, of the digest produced by this message digest. + * + * @return the size, in bytes, of the digest produced by this message digest. + */ + public int getDigestSize() + { + return DIGEST_LENGTH; + } + /** + * close the digest, producing the final digest value. The doFinal + * call leaves the digest reset. + * + * @param out the array the digest is to be copied into. + * @param outOff the offset into the out array the digest is to start at. + */ + public int doFinal(byte[] out, int outOff) + { + // add padding + byte paddingByte = (byte)(M.length-mOff); + for (int i=mOff;i 0)) + { + update(in[inOff]); + inOff++; + len--; + } + + // + // process whole words. + // + while (len > 16) + { + System.arraycopy(in,inOff,M,0,16); + processCheckSum(M); + processBlock(M); + len -= 16; + inOff += 16; + } + + // + // load in the remainder. + // + while (len > 0) + { + update(in[inOff]); + inOff++; + len--; + } + } + protected void processCheckSum(byte[] m) + { + int L = C[15]; + for (int i=0;i<16;i++) + { + C[i] ^= S[(m[i] ^ L) & 0xff]; + L = C[i]; + } + } + protected void processBlock(byte[] m) + { + for (int i=0;i<16;i++) + { + X[i+16] = m[i]; + X[i+32] = (byte)(m[i] ^ X[i]); + } + // encrypt block + int t = 0; + + for (int j=0;j<18;j++) + { + for (int k=0;k<48;k++) + { + t = X[k] ^= S[t]; + t = t & 0xff; + } + t = (t + j)%256; + } + } + // 256-byte random permutation constructed from the digits of PI + private static final byte[] S = { + (byte)41,(byte)46,(byte)67,(byte)201,(byte)162,(byte)216,(byte)124, + (byte)1,(byte)61,(byte)54,(byte)84,(byte)161,(byte)236,(byte)240, + (byte)6,(byte)19,(byte)98,(byte)167,(byte)5,(byte)243,(byte)192, + (byte)199,(byte)115,(byte)140,(byte)152,(byte)147,(byte)43,(byte)217, + (byte)188,(byte)76,(byte)130,(byte)202,(byte)30,(byte)155,(byte)87, + (byte)60,(byte)253,(byte)212,(byte)224,(byte)22,(byte)103,(byte)66, + (byte)111,(byte)24,(byte)138,(byte)23,(byte)229,(byte)18,(byte)190, + (byte)78,(byte)196,(byte)214,(byte)218,(byte)158,(byte)222,(byte)73, + (byte)160,(byte)251,(byte)245,(byte)142,(byte)187,(byte)47,(byte)238, + (byte)122,(byte)169,(byte)104,(byte)121,(byte)145,(byte)21,(byte)178, + (byte)7,(byte)63,(byte)148,(byte)194,(byte)16,(byte)137,(byte)11, + (byte)34,(byte)95,(byte)33,(byte)128,(byte)127,(byte)93,(byte)154, + (byte)90,(byte)144,(byte)50,(byte)39,(byte)53,(byte)62,(byte)204, + (byte)231,(byte)191,(byte)247,(byte)151,(byte)3,(byte)255,(byte)25, + (byte)48,(byte)179,(byte)72,(byte)165,(byte)181,(byte)209,(byte)215, + (byte)94,(byte)146,(byte)42,(byte)172,(byte)86,(byte)170,(byte)198, + (byte)79,(byte)184,(byte)56,(byte)210,(byte)150,(byte)164,(byte)125, + (byte)182,(byte)118,(byte)252,(byte)107,(byte)226,(byte)156,(byte)116, + (byte)4,(byte)241,(byte)69,(byte)157,(byte)112,(byte)89,(byte)100, + (byte)113,(byte)135,(byte)32,(byte)134,(byte)91,(byte)207,(byte)101, + (byte)230,(byte)45,(byte)168,(byte)2,(byte)27,(byte)96,(byte)37, + (byte)173,(byte)174,(byte)176,(byte)185,(byte)246,(byte)28,(byte)70, + (byte)97,(byte)105,(byte)52,(byte)64,(byte)126,(byte)15,(byte)85, + (byte)71,(byte)163,(byte)35,(byte)221,(byte)81,(byte)175,(byte)58, + (byte)195,(byte)92,(byte)249,(byte)206,(byte)186,(byte)197,(byte)234, + (byte)38,(byte)44,(byte)83,(byte)13,(byte)110,(byte)133,(byte)40, + (byte)132, 9,(byte)211,(byte)223,(byte)205,(byte)244,(byte)65, + (byte)129,(byte)77,(byte)82,(byte)106,(byte)220,(byte)55,(byte)200, + (byte)108,(byte)193,(byte)171,(byte)250,(byte)36,(byte)225,(byte)123, + (byte)8,(byte)12,(byte)189,(byte)177,(byte)74,(byte)120,(byte)136, + (byte)149,(byte)139,(byte)227,(byte)99,(byte)232,(byte)109,(byte)233, + (byte)203,(byte)213,(byte)254,(byte)59,(byte)0,(byte)29,(byte)57, + (byte)242,(byte)239,(byte)183,(byte)14,(byte)102,(byte)88,(byte)208, + (byte)228,(byte)166,(byte)119,(byte)114,(byte)248,(byte)235,(byte)117, + (byte)75,(byte)10,(byte)49,(byte)68,(byte)80,(byte)180,(byte)143, + (byte)237,(byte)31,(byte)26,(byte)219,(byte)153,(byte)141,(byte)51, + (byte)159,(byte)17,(byte)131,(byte)20 + }; + + public int getByteLength() + { + return 16; + } +} + + diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/MD4Digest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/MD4Digest.java new file mode 100644 index 000000000..0a95de20e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/MD4Digest.java @@ -0,0 +1,270 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + + +/** + * implementation of MD4 as RFC 1320 by R. Rivest, MIT Laboratory for + * Computer Science and RSA Data Security, Inc. + *

+ * NOTE: This algorithm is only included for backwards compatability + * with legacy applications, it's not secure, don't use it for anything new! + */ +public class MD4Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 16; + + private int H1, H2, H3, H4; // IV's + + private int[] X = new int[16]; + private int xOff; + + /** + * Standard constructor + */ + public MD4Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public MD4Digest(MD4Digest t) + { + super(t); + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "MD4"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) + | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)(bitLength >>> 32); + } + + private void unpackWord( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)word; + out[outOff + 1] = (byte)(word >>> 8); + out[outOff + 2] = (byte)(word >>> 16); + out[outOff + 3] = (byte)(word >>> 24); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(H1, out, outOff); + unpackWord(H2, out, outOff + 4); + unpackWord(H3, out, outOff + 8); + unpackWord(H4, out, outOff + 12); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + public void reset() + { + super.reset(); + + H1 = 0x67452301; + H2 = 0xefcdab89; + H3 = 0x98badcfe; + H4 = 0x10325476; + + xOff = 0; + + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + // + // round 1 left rotates + // + private static final int S11 = 3; + private static final int S12 = 7; + private static final int S13 = 11; + private static final int S14 = 19; + + // + // round 2 left rotates + // + private static final int S21 = 3; + private static final int S22 = 5; + private static final int S23 = 9; + private static final int S24 = 13; + + // + // round 3 left rotates + // + private static final int S31 = 3; + private static final int S32 = 9; + private static final int S33 = 11; + private static final int S34 = 15; + + /* + * rotate int x left n bits. + */ + private int rotateLeft( + int x, + int n) + { + return (x << n) | (x >>> (32 - n)); + } + + /* + * F, G, H and I are the basic MD4 functions. + */ + private int F( + int u, + int v, + int w) + { + return (u & v) | (~u & w); + } + + private int G( + int u, + int v, + int w) + { + return (u & v) | (u & w) | (v & w); + } + + private int H( + int u, + int v, + int w) + { + return u ^ v ^ w; + } + + protected void processBlock() + { + int a = H1; + int b = H2; + int c = H3; + int d = H4; + + // + // Round 1 - F cycle, 16 times. + // + a = rotateLeft(a + F(b, c, d) + X[ 0], S11); + d = rotateLeft(d + F(a, b, c) + X[ 1], S12); + c = rotateLeft(c + F(d, a, b) + X[ 2], S13); + b = rotateLeft(b + F(c, d, a) + X[ 3], S14); + a = rotateLeft(a + F(b, c, d) + X[ 4], S11); + d = rotateLeft(d + F(a, b, c) + X[ 5], S12); + c = rotateLeft(c + F(d, a, b) + X[ 6], S13); + b = rotateLeft(b + F(c, d, a) + X[ 7], S14); + a = rotateLeft(a + F(b, c, d) + X[ 8], S11); + d = rotateLeft(d + F(a, b, c) + X[ 9], S12); + c = rotateLeft(c + F(d, a, b) + X[10], S13); + b = rotateLeft(b + F(c, d, a) + X[11], S14); + a = rotateLeft(a + F(b, c, d) + X[12], S11); + d = rotateLeft(d + F(a, b, c) + X[13], S12); + c = rotateLeft(c + F(d, a, b) + X[14], S13); + b = rotateLeft(b + F(c, d, a) + X[15], S14); + + // + // Round 2 - G cycle, 16 times. + // + a = rotateLeft(a + G(b, c, d) + X[ 0] + 0x5a827999, S21); + d = rotateLeft(d + G(a, b, c) + X[ 4] + 0x5a827999, S22); + c = rotateLeft(c + G(d, a, b) + X[ 8] + 0x5a827999, S23); + b = rotateLeft(b + G(c, d, a) + X[12] + 0x5a827999, S24); + a = rotateLeft(a + G(b, c, d) + X[ 1] + 0x5a827999, S21); + d = rotateLeft(d + G(a, b, c) + X[ 5] + 0x5a827999, S22); + c = rotateLeft(c + G(d, a, b) + X[ 9] + 0x5a827999, S23); + b = rotateLeft(b + G(c, d, a) + X[13] + 0x5a827999, S24); + a = rotateLeft(a + G(b, c, d) + X[ 2] + 0x5a827999, S21); + d = rotateLeft(d + G(a, b, c) + X[ 6] + 0x5a827999, S22); + c = rotateLeft(c + G(d, a, b) + X[10] + 0x5a827999, S23); + b = rotateLeft(b + G(c, d, a) + X[14] + 0x5a827999, S24); + a = rotateLeft(a + G(b, c, d) + X[ 3] + 0x5a827999, S21); + d = rotateLeft(d + G(a, b, c) + X[ 7] + 0x5a827999, S22); + c = rotateLeft(c + G(d, a, b) + X[11] + 0x5a827999, S23); + b = rotateLeft(b + G(c, d, a) + X[15] + 0x5a827999, S24); + + // + // Round 3 - H cycle, 16 times. + // + a = rotateLeft(a + H(b, c, d) + X[ 0] + 0x6ed9eba1, S31); + d = rotateLeft(d + H(a, b, c) + X[ 8] + 0x6ed9eba1, S32); + c = rotateLeft(c + H(d, a, b) + X[ 4] + 0x6ed9eba1, S33); + b = rotateLeft(b + H(c, d, a) + X[12] + 0x6ed9eba1, S34); + a = rotateLeft(a + H(b, c, d) + X[ 2] + 0x6ed9eba1, S31); + d = rotateLeft(d + H(a, b, c) + X[10] + 0x6ed9eba1, S32); + c = rotateLeft(c + H(d, a, b) + X[ 6] + 0x6ed9eba1, S33); + b = rotateLeft(b + H(c, d, a) + X[14] + 0x6ed9eba1, S34); + a = rotateLeft(a + H(b, c, d) + X[ 1] + 0x6ed9eba1, S31); + d = rotateLeft(d + H(a, b, c) + X[ 9] + 0x6ed9eba1, S32); + c = rotateLeft(c + H(d, a, b) + X[ 5] + 0x6ed9eba1, S33); + b = rotateLeft(b + H(c, d, a) + X[13] + 0x6ed9eba1, S34); + a = rotateLeft(a + H(b, c, d) + X[ 3] + 0x6ed9eba1, S31); + d = rotateLeft(d + H(a, b, c) + X[11] + 0x6ed9eba1, S32); + c = rotateLeft(c + H(d, a, b) + X[ 7] + 0x6ed9eba1, S33); + b = rotateLeft(b + H(c, d, a) + X[15] + 0x6ed9eba1, S34); + + H1 += a; + H2 += b; + H3 += c; + H4 += d; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/MD5Digest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/MD5Digest.java new file mode 100644 index 000000000..3f0f59cdf --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/MD5Digest.java @@ -0,0 +1,302 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + + +/** + * implementation of MD5 as outlined in "Handbook of Applied Cryptography", pages 346 - 347. + */ +public class MD5Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 16; + + private int H1, H2, H3, H4; // IV's + + private int[] X = new int[16]; + private int xOff; + + /** + * Standard constructor + */ + public MD5Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public MD5Digest(MD5Digest t) + { + super(t); + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "MD5"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) + | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)(bitLength >>> 32); + } + + private void unpackWord( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)word; + out[outOff + 1] = (byte)(word >>> 8); + out[outOff + 2] = (byte)(word >>> 16); + out[outOff + 3] = (byte)(word >>> 24); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(H1, out, outOff); + unpackWord(H2, out, outOff + 4); + unpackWord(H3, out, outOff + 8); + unpackWord(H4, out, outOff + 12); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + public void reset() + { + super.reset(); + + H1 = 0x67452301; + H2 = 0xefcdab89; + H3 = 0x98badcfe; + H4 = 0x10325476; + + xOff = 0; + + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + // + // round 1 left rotates + // + private static final int S11 = 7; + private static final int S12 = 12; + private static final int S13 = 17; + private static final int S14 = 22; + + // + // round 2 left rotates + // + private static final int S21 = 5; + private static final int S22 = 9; + private static final int S23 = 14; + private static final int S24 = 20; + + // + // round 3 left rotates + // + private static final int S31 = 4; + private static final int S32 = 11; + private static final int S33 = 16; + private static final int S34 = 23; + + // + // round 4 left rotates + // + private static final int S41 = 6; + private static final int S42 = 10; + private static final int S43 = 15; + private static final int S44 = 21; + + /* + * rotate int x left n bits. + */ + private int rotateLeft( + int x, + int n) + { + return (x << n) | (x >>> (32 - n)); + } + + /* + * F, G, H and I are the basic MD5 functions. + */ + private int F( + int u, + int v, + int w) + { + return (u & v) | (~u & w); + } + + private int G( + int u, + int v, + int w) + { + return (u & w) | (v & ~w); + } + + private int H( + int u, + int v, + int w) + { + return u ^ v ^ w; + } + + private int K( + int u, + int v, + int w) + { + return v ^ (u | ~w); + } + + protected void processBlock() + { + int a = H1; + int b = H2; + int c = H3; + int d = H4; + + // + // Round 1 - F cycle, 16 times. + // + a = rotateLeft(a + F(b, c, d) + X[ 0] + 0xd76aa478, S11) + b; + d = rotateLeft(d + F(a, b, c) + X[ 1] + 0xe8c7b756, S12) + a; + c = rotateLeft(c + F(d, a, b) + X[ 2] + 0x242070db, S13) + d; + b = rotateLeft(b + F(c, d, a) + X[ 3] + 0xc1bdceee, S14) + c; + a = rotateLeft(a + F(b, c, d) + X[ 4] + 0xf57c0faf, S11) + b; + d = rotateLeft(d + F(a, b, c) + X[ 5] + 0x4787c62a, S12) + a; + c = rotateLeft(c + F(d, a, b) + X[ 6] + 0xa8304613, S13) + d; + b = rotateLeft(b + F(c, d, a) + X[ 7] + 0xfd469501, S14) + c; + a = rotateLeft(a + F(b, c, d) + X[ 8] + 0x698098d8, S11) + b; + d = rotateLeft(d + F(a, b, c) + X[ 9] + 0x8b44f7af, S12) + a; + c = rotateLeft(c + F(d, a, b) + X[10] + 0xffff5bb1, S13) + d; + b = rotateLeft(b + F(c, d, a) + X[11] + 0x895cd7be, S14) + c; + a = rotateLeft(a + F(b, c, d) + X[12] + 0x6b901122, S11) + b; + d = rotateLeft(d + F(a, b, c) + X[13] + 0xfd987193, S12) + a; + c = rotateLeft(c + F(d, a, b) + X[14] + 0xa679438e, S13) + d; + b = rotateLeft(b + F(c, d, a) + X[15] + 0x49b40821, S14) + c; + + // + // Round 2 - G cycle, 16 times. + // + a = rotateLeft(a + G(b, c, d) + X[ 1] + 0xf61e2562, S21) + b; + d = rotateLeft(d + G(a, b, c) + X[ 6] + 0xc040b340, S22) + a; + c = rotateLeft(c + G(d, a, b) + X[11] + 0x265e5a51, S23) + d; + b = rotateLeft(b + G(c, d, a) + X[ 0] + 0xe9b6c7aa, S24) + c; + a = rotateLeft(a + G(b, c, d) + X[ 5] + 0xd62f105d, S21) + b; + d = rotateLeft(d + G(a, b, c) + X[10] + 0x02441453, S22) + a; + c = rotateLeft(c + G(d, a, b) + X[15] + 0xd8a1e681, S23) + d; + b = rotateLeft(b + G(c, d, a) + X[ 4] + 0xe7d3fbc8, S24) + c; + a = rotateLeft(a + G(b, c, d) + X[ 9] + 0x21e1cde6, S21) + b; + d = rotateLeft(d + G(a, b, c) + X[14] + 0xc33707d6, S22) + a; + c = rotateLeft(c + G(d, a, b) + X[ 3] + 0xf4d50d87, S23) + d; + b = rotateLeft(b + G(c, d, a) + X[ 8] + 0x455a14ed, S24) + c; + a = rotateLeft(a + G(b, c, d) + X[13] + 0xa9e3e905, S21) + b; + d = rotateLeft(d + G(a, b, c) + X[ 2] + 0xfcefa3f8, S22) + a; + c = rotateLeft(c + G(d, a, b) + X[ 7] + 0x676f02d9, S23) + d; + b = rotateLeft(b + G(c, d, a) + X[12] + 0x8d2a4c8a, S24) + c; + + // + // Round 3 - H cycle, 16 times. + // + a = rotateLeft(a + H(b, c, d) + X[ 5] + 0xfffa3942, S31) + b; + d = rotateLeft(d + H(a, b, c) + X[ 8] + 0x8771f681, S32) + a; + c = rotateLeft(c + H(d, a, b) + X[11] + 0x6d9d6122, S33) + d; + b = rotateLeft(b + H(c, d, a) + X[14] + 0xfde5380c, S34) + c; + a = rotateLeft(a + H(b, c, d) + X[ 1] + 0xa4beea44, S31) + b; + d = rotateLeft(d + H(a, b, c) + X[ 4] + 0x4bdecfa9, S32) + a; + c = rotateLeft(c + H(d, a, b) + X[ 7] + 0xf6bb4b60, S33) + d; + b = rotateLeft(b + H(c, d, a) + X[10] + 0xbebfbc70, S34) + c; + a = rotateLeft(a + H(b, c, d) + X[13] + 0x289b7ec6, S31) + b; + d = rotateLeft(d + H(a, b, c) + X[ 0] + 0xeaa127fa, S32) + a; + c = rotateLeft(c + H(d, a, b) + X[ 3] + 0xd4ef3085, S33) + d; + b = rotateLeft(b + H(c, d, a) + X[ 6] + 0x04881d05, S34) + c; + a = rotateLeft(a + H(b, c, d) + X[ 9] + 0xd9d4d039, S31) + b; + d = rotateLeft(d + H(a, b, c) + X[12] + 0xe6db99e5, S32) + a; + c = rotateLeft(c + H(d, a, b) + X[15] + 0x1fa27cf8, S33) + d; + b = rotateLeft(b + H(c, d, a) + X[ 2] + 0xc4ac5665, S34) + c; + + // + // Round 4 - K cycle, 16 times. + // + a = rotateLeft(a + K(b, c, d) + X[ 0] + 0xf4292244, S41) + b; + d = rotateLeft(d + K(a, b, c) + X[ 7] + 0x432aff97, S42) + a; + c = rotateLeft(c + K(d, a, b) + X[14] + 0xab9423a7, S43) + d; + b = rotateLeft(b + K(c, d, a) + X[ 5] + 0xfc93a039, S44) + c; + a = rotateLeft(a + K(b, c, d) + X[12] + 0x655b59c3, S41) + b; + d = rotateLeft(d + K(a, b, c) + X[ 3] + 0x8f0ccc92, S42) + a; + c = rotateLeft(c + K(d, a, b) + X[10] + 0xffeff47d, S43) + d; + b = rotateLeft(b + K(c, d, a) + X[ 1] + 0x85845dd1, S44) + c; + a = rotateLeft(a + K(b, c, d) + X[ 8] + 0x6fa87e4f, S41) + b; + d = rotateLeft(d + K(a, b, c) + X[15] + 0xfe2ce6e0, S42) + a; + c = rotateLeft(c + K(d, a, b) + X[ 6] + 0xa3014314, S43) + d; + b = rotateLeft(b + K(c, d, a) + X[13] + 0x4e0811a1, S44) + c; + a = rotateLeft(a + K(b, c, d) + X[ 4] + 0xf7537e82, S41) + b; + d = rotateLeft(d + K(a, b, c) + X[11] + 0xbd3af235, S42) + a; + c = rotateLeft(c + K(d, a, b) + X[ 2] + 0x2ad7d2bb, S43) + d; + b = rotateLeft(b + K(c, d, a) + X[ 9] + 0xeb86d391, S44) + c; + + H1 += a; + H2 += b; + H3 += c; + H4 += d; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD128Digest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD128Digest.java new file mode 100644 index 000000000..e2aab357c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD128Digest.java @@ -0,0 +1,461 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + + +/** + * implementation of RIPEMD128 + */ +public class RIPEMD128Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 16; + + private int H0, H1, H2, H3; // IV's + + private int[] X = new int[16]; + private int xOff; + + /** + * Standard constructor + */ + public RIPEMD128Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public RIPEMD128Digest(RIPEMD128Digest t) + { + super(t); + + H0 = t.H0; + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "RIPEMD128"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) + | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)(bitLength >>> 32); + } + + private void unpackWord( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)word; + out[outOff + 1] = (byte)(word >>> 8); + out[outOff + 2] = (byte)(word >>> 16); + out[outOff + 3] = (byte)(word >>> 24); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(H0, out, outOff); + unpackWord(H1, out, outOff + 4); + unpackWord(H2, out, outOff + 8); + unpackWord(H3, out, outOff + 12); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + public void reset() + { + super.reset(); + + H0 = 0x67452301; + H1 = 0xefcdab89; + H2 = 0x98badcfe; + H3 = 0x10325476; + + xOff = 0; + + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + /* + * rotate int x left n bits. + */ + private int RL( + int x, + int n) + { + return (x << n) | (x >>> (32 - n)); + } + + /* + * f1,f2,f3,f4 are the basic RIPEMD128 functions. + */ + + /* + * F + */ + private int f1( + int x, + int y, + int z) + { + return x ^ y ^ z; + } + + /* + * G + */ + private int f2( + int x, + int y, + int z) + { + return (x & y) | (~x & z); + } + + /* + * H + */ + private int f3( + int x, + int y, + int z) + { + return (x | ~y) ^ z; + } + + /* + * I + */ + private int f4( + int x, + int y, + int z) + { + return (x & z) | (y & ~z); + } + + private int F1( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f1(b, c, d) + x, s); + } + + private int F2( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f2(b, c, d) + x + 0x5a827999, s); + } + + private int F3( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f3(b, c, d) + x + 0x6ed9eba1, s); + } + + private int F4( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f4(b, c, d) + x + 0x8f1bbcdc, s); + } + + private int FF1( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f1(b, c, d) + x, s); + } + + private int FF2( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f2(b, c, d) + x + 0x6d703ef3, s); + } + + private int FF3( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f3(b, c, d) + x + 0x5c4dd124, s); + } + + private int FF4( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f4(b, c, d) + x + 0x50a28be6, s); + } + + protected void processBlock() + { + int a, aa; + int b, bb; + int c, cc; + int d, dd; + + a = aa = H0; + b = bb = H1; + c = cc = H2; + d = dd = H3; + + // + // Round 1 + // + a = F1(a, b, c, d, X[ 0], 11); + d = F1(d, a, b, c, X[ 1], 14); + c = F1(c, d, a, b, X[ 2], 15); + b = F1(b, c, d, a, X[ 3], 12); + a = F1(a, b, c, d, X[ 4], 5); + d = F1(d, a, b, c, X[ 5], 8); + c = F1(c, d, a, b, X[ 6], 7); + b = F1(b, c, d, a, X[ 7], 9); + a = F1(a, b, c, d, X[ 8], 11); + d = F1(d, a, b, c, X[ 9], 13); + c = F1(c, d, a, b, X[10], 14); + b = F1(b, c, d, a, X[11], 15); + a = F1(a, b, c, d, X[12], 6); + d = F1(d, a, b, c, X[13], 7); + c = F1(c, d, a, b, X[14], 9); + b = F1(b, c, d, a, X[15], 8); + + // + // Round 2 + // + a = F2(a, b, c, d, X[ 7], 7); + d = F2(d, a, b, c, X[ 4], 6); + c = F2(c, d, a, b, X[13], 8); + b = F2(b, c, d, a, X[ 1], 13); + a = F2(a, b, c, d, X[10], 11); + d = F2(d, a, b, c, X[ 6], 9); + c = F2(c, d, a, b, X[15], 7); + b = F2(b, c, d, a, X[ 3], 15); + a = F2(a, b, c, d, X[12], 7); + d = F2(d, a, b, c, X[ 0], 12); + c = F2(c, d, a, b, X[ 9], 15); + b = F2(b, c, d, a, X[ 5], 9); + a = F2(a, b, c, d, X[ 2], 11); + d = F2(d, a, b, c, X[14], 7); + c = F2(c, d, a, b, X[11], 13); + b = F2(b, c, d, a, X[ 8], 12); + + // + // Round 3 + // + a = F3(a, b, c, d, X[ 3], 11); + d = F3(d, a, b, c, X[10], 13); + c = F3(c, d, a, b, X[14], 6); + b = F3(b, c, d, a, X[ 4], 7); + a = F3(a, b, c, d, X[ 9], 14); + d = F3(d, a, b, c, X[15], 9); + c = F3(c, d, a, b, X[ 8], 13); + b = F3(b, c, d, a, X[ 1], 15); + a = F3(a, b, c, d, X[ 2], 14); + d = F3(d, a, b, c, X[ 7], 8); + c = F3(c, d, a, b, X[ 0], 13); + b = F3(b, c, d, a, X[ 6], 6); + a = F3(a, b, c, d, X[13], 5); + d = F3(d, a, b, c, X[11], 12); + c = F3(c, d, a, b, X[ 5], 7); + b = F3(b, c, d, a, X[12], 5); + + // + // Round 4 + // + a = F4(a, b, c, d, X[ 1], 11); + d = F4(d, a, b, c, X[ 9], 12); + c = F4(c, d, a, b, X[11], 14); + b = F4(b, c, d, a, X[10], 15); + a = F4(a, b, c, d, X[ 0], 14); + d = F4(d, a, b, c, X[ 8], 15); + c = F4(c, d, a, b, X[12], 9); + b = F4(b, c, d, a, X[ 4], 8); + a = F4(a, b, c, d, X[13], 9); + d = F4(d, a, b, c, X[ 3], 14); + c = F4(c, d, a, b, X[ 7], 5); + b = F4(b, c, d, a, X[15], 6); + a = F4(a, b, c, d, X[14], 8); + d = F4(d, a, b, c, X[ 5], 6); + c = F4(c, d, a, b, X[ 6], 5); + b = F4(b, c, d, a, X[ 2], 12); + + // + // Parallel round 1 + // + aa = FF4(aa, bb, cc, dd, X[ 5], 8); + dd = FF4(dd, aa, bb, cc, X[14], 9); + cc = FF4(cc, dd, aa, bb, X[ 7], 9); + bb = FF4(bb, cc, dd, aa, X[ 0], 11); + aa = FF4(aa, bb, cc, dd, X[ 9], 13); + dd = FF4(dd, aa, bb, cc, X[ 2], 15); + cc = FF4(cc, dd, aa, bb, X[11], 15); + bb = FF4(bb, cc, dd, aa, X[ 4], 5); + aa = FF4(aa, bb, cc, dd, X[13], 7); + dd = FF4(dd, aa, bb, cc, X[ 6], 7); + cc = FF4(cc, dd, aa, bb, X[15], 8); + bb = FF4(bb, cc, dd, aa, X[ 8], 11); + aa = FF4(aa, bb, cc, dd, X[ 1], 14); + dd = FF4(dd, aa, bb, cc, X[10], 14); + cc = FF4(cc, dd, aa, bb, X[ 3], 12); + bb = FF4(bb, cc, dd, aa, X[12], 6); + + // + // Parallel round 2 + // + aa = FF3(aa, bb, cc, dd, X[ 6], 9); + dd = FF3(dd, aa, bb, cc, X[11], 13); + cc = FF3(cc, dd, aa, bb, X[ 3], 15); + bb = FF3(bb, cc, dd, aa, X[ 7], 7); + aa = FF3(aa, bb, cc, dd, X[ 0], 12); + dd = FF3(dd, aa, bb, cc, X[13], 8); + cc = FF3(cc, dd, aa, bb, X[ 5], 9); + bb = FF3(bb, cc, dd, aa, X[10], 11); + aa = FF3(aa, bb, cc, dd, X[14], 7); + dd = FF3(dd, aa, bb, cc, X[15], 7); + cc = FF3(cc, dd, aa, bb, X[ 8], 12); + bb = FF3(bb, cc, dd, aa, X[12], 7); + aa = FF3(aa, bb, cc, dd, X[ 4], 6); + dd = FF3(dd, aa, bb, cc, X[ 9], 15); + cc = FF3(cc, dd, aa, bb, X[ 1], 13); + bb = FF3(bb, cc, dd, aa, X[ 2], 11); + + // + // Parallel round 3 + // + aa = FF2(aa, bb, cc, dd, X[15], 9); + dd = FF2(dd, aa, bb, cc, X[ 5], 7); + cc = FF2(cc, dd, aa, bb, X[ 1], 15); + bb = FF2(bb, cc, dd, aa, X[ 3], 11); + aa = FF2(aa, bb, cc, dd, X[ 7], 8); + dd = FF2(dd, aa, bb, cc, X[14], 6); + cc = FF2(cc, dd, aa, bb, X[ 6], 6); + bb = FF2(bb, cc, dd, aa, X[ 9], 14); + aa = FF2(aa, bb, cc, dd, X[11], 12); + dd = FF2(dd, aa, bb, cc, X[ 8], 13); + cc = FF2(cc, dd, aa, bb, X[12], 5); + bb = FF2(bb, cc, dd, aa, X[ 2], 14); + aa = FF2(aa, bb, cc, dd, X[10], 13); + dd = FF2(dd, aa, bb, cc, X[ 0], 13); + cc = FF2(cc, dd, aa, bb, X[ 4], 7); + bb = FF2(bb, cc, dd, aa, X[13], 5); + + // + // Parallel round 4 + // + aa = FF1(aa, bb, cc, dd, X[ 8], 15); + dd = FF1(dd, aa, bb, cc, X[ 6], 5); + cc = FF1(cc, dd, aa, bb, X[ 4], 8); + bb = FF1(bb, cc, dd, aa, X[ 1], 11); + aa = FF1(aa, bb, cc, dd, X[ 3], 14); + dd = FF1(dd, aa, bb, cc, X[11], 14); + cc = FF1(cc, dd, aa, bb, X[15], 6); + bb = FF1(bb, cc, dd, aa, X[ 0], 14); + aa = FF1(aa, bb, cc, dd, X[ 5], 6); + dd = FF1(dd, aa, bb, cc, X[12], 9); + cc = FF1(cc, dd, aa, bb, X[ 2], 12); + bb = FF1(bb, cc, dd, aa, X[13], 9); + aa = FF1(aa, bb, cc, dd, X[ 9], 12); + dd = FF1(dd, aa, bb, cc, X[ 7], 5); + cc = FF1(cc, dd, aa, bb, X[10], 15); + bb = FF1(bb, cc, dd, aa, X[14], 8); + + dd += c + H1; // final result for H0 + + // + // combine the results + // + H1 = H2 + d + aa; + H2 = H3 + a + bb; + H3 = H0 + b + cc; + H0 = dd; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD160Digest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD160Digest.java new file mode 100644 index 000000000..bb6634415 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD160Digest.java @@ -0,0 +1,422 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + + +/** + * implementation of RIPEMD see, + * http://www.esat.kuleuven.ac.be/~bosselae/ripemd160.html + */ +public class RIPEMD160Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 20; + + private int H0, H1, H2, H3, H4; // IV's + + private int[] X = new int[16]; + private int xOff; + + /** + * Standard constructor + */ + public RIPEMD160Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public RIPEMD160Digest(RIPEMD160Digest t) + { + super(t); + + H0 = t.H0; + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "RIPEMD160"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) + | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)(bitLength >>> 32); + } + + private void unpackWord( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)word; + out[outOff + 1] = (byte)(word >>> 8); + out[outOff + 2] = (byte)(word >>> 16); + out[outOff + 3] = (byte)(word >>> 24); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(H0, out, outOff); + unpackWord(H1, out, outOff + 4); + unpackWord(H2, out, outOff + 8); + unpackWord(H3, out, outOff + 12); + unpackWord(H4, out, outOff + 16); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + public void reset() + { + super.reset(); + + H0 = 0x67452301; + H1 = 0xefcdab89; + H2 = 0x98badcfe; + H3 = 0x10325476; + H4 = 0xc3d2e1f0; + + xOff = 0; + + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + /* + * rotate int x left n bits. + */ + private int RL( + int x, + int n) + { + return (x << n) | (x >>> (32 - n)); + } + + /* + * f1,f2,f3,f4,f5 are the basic RIPEMD160 functions. + */ + + /* + * rounds 0-15 + */ + private int f1( + int x, + int y, + int z) + { + return x ^ y ^ z; + } + + /* + * rounds 16-31 + */ + private int f2( + int x, + int y, + int z) + { + return (x & y) | (~x & z); + } + + /* + * rounds 32-47 + */ + private int f3( + int x, + int y, + int z) + { + return (x | ~y) ^ z; + } + + /* + * rounds 48-63 + */ + private int f4( + int x, + int y, + int z) + { + return (x & z) | (y & ~z); + } + + /* + * rounds 64-79 + */ + private int f5( + int x, + int y, + int z) + { + return x ^ (y | ~z); + } + + protected void processBlock() + { + int a, aa; + int b, bb; + int c, cc; + int d, dd; + int e, ee; + + a = aa = H0; + b = bb = H1; + c = cc = H2; + d = dd = H3; + e = ee = H4; + + // + // Rounds 1 - 16 + // + // left + a = RL(a + f1(b,c,d) + X[ 0], 11) + e; c = RL(c, 10); + e = RL(e + f1(a,b,c) + X[ 1], 14) + d; b = RL(b, 10); + d = RL(d + f1(e,a,b) + X[ 2], 15) + c; a = RL(a, 10); + c = RL(c + f1(d,e,a) + X[ 3], 12) + b; e = RL(e, 10); + b = RL(b + f1(c,d,e) + X[ 4], 5) + a; d = RL(d, 10); + a = RL(a + f1(b,c,d) + X[ 5], 8) + e; c = RL(c, 10); + e = RL(e + f1(a,b,c) + X[ 6], 7) + d; b = RL(b, 10); + d = RL(d + f1(e,a,b) + X[ 7], 9) + c; a = RL(a, 10); + c = RL(c + f1(d,e,a) + X[ 8], 11) + b; e = RL(e, 10); + b = RL(b + f1(c,d,e) + X[ 9], 13) + a; d = RL(d, 10); + a = RL(a + f1(b,c,d) + X[10], 14) + e; c = RL(c, 10); + e = RL(e + f1(a,b,c) + X[11], 15) + d; b = RL(b, 10); + d = RL(d + f1(e,a,b) + X[12], 6) + c; a = RL(a, 10); + c = RL(c + f1(d,e,a) + X[13], 7) + b; e = RL(e, 10); + b = RL(b + f1(c,d,e) + X[14], 9) + a; d = RL(d, 10); + a = RL(a + f1(b,c,d) + X[15], 8) + e; c = RL(c, 10); + + // right + aa = RL(aa + f5(bb,cc,dd) + X[ 5] + 0x50a28be6, 8) + ee; cc = RL(cc, 10); + ee = RL(ee + f5(aa,bb,cc) + X[14] + 0x50a28be6, 9) + dd; bb = RL(bb, 10); + dd = RL(dd + f5(ee,aa,bb) + X[ 7] + 0x50a28be6, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f5(dd,ee,aa) + X[ 0] + 0x50a28be6, 11) + bb; ee = RL(ee, 10); + bb = RL(bb + f5(cc,dd,ee) + X[ 9] + 0x50a28be6, 13) + aa; dd = RL(dd, 10); + aa = RL(aa + f5(bb,cc,dd) + X[ 2] + 0x50a28be6, 15) + ee; cc = RL(cc, 10); + ee = RL(ee + f5(aa,bb,cc) + X[11] + 0x50a28be6, 15) + dd; bb = RL(bb, 10); + dd = RL(dd + f5(ee,aa,bb) + X[ 4] + 0x50a28be6, 5) + cc; aa = RL(aa, 10); + cc = RL(cc + f5(dd,ee,aa) + X[13] + 0x50a28be6, 7) + bb; ee = RL(ee, 10); + bb = RL(bb + f5(cc,dd,ee) + X[ 6] + 0x50a28be6, 7) + aa; dd = RL(dd, 10); + aa = RL(aa + f5(bb,cc,dd) + X[15] + 0x50a28be6, 8) + ee; cc = RL(cc, 10); + ee = RL(ee + f5(aa,bb,cc) + X[ 8] + 0x50a28be6, 11) + dd; bb = RL(bb, 10); + dd = RL(dd + f5(ee,aa,bb) + X[ 1] + 0x50a28be6, 14) + cc; aa = RL(aa, 10); + cc = RL(cc + f5(dd,ee,aa) + X[10] + 0x50a28be6, 14) + bb; ee = RL(ee, 10); + bb = RL(bb + f5(cc,dd,ee) + X[ 3] + 0x50a28be6, 12) + aa; dd = RL(dd, 10); + aa = RL(aa + f5(bb,cc,dd) + X[12] + 0x50a28be6, 6) + ee; cc = RL(cc, 10); + + // + // Rounds 16-31 + // + // left + e = RL(e + f2(a,b,c) + X[ 7] + 0x5a827999, 7) + d; b = RL(b, 10); + d = RL(d + f2(e,a,b) + X[ 4] + 0x5a827999, 6) + c; a = RL(a, 10); + c = RL(c + f2(d,e,a) + X[13] + 0x5a827999, 8) + b; e = RL(e, 10); + b = RL(b + f2(c,d,e) + X[ 1] + 0x5a827999, 13) + a; d = RL(d, 10); + a = RL(a + f2(b,c,d) + X[10] + 0x5a827999, 11) + e; c = RL(c, 10); + e = RL(e + f2(a,b,c) + X[ 6] + 0x5a827999, 9) + d; b = RL(b, 10); + d = RL(d + f2(e,a,b) + X[15] + 0x5a827999, 7) + c; a = RL(a, 10); + c = RL(c + f2(d,e,a) + X[ 3] + 0x5a827999, 15) + b; e = RL(e, 10); + b = RL(b + f2(c,d,e) + X[12] + 0x5a827999, 7) + a; d = RL(d, 10); + a = RL(a + f2(b,c,d) + X[ 0] + 0x5a827999, 12) + e; c = RL(c, 10); + e = RL(e + f2(a,b,c) + X[ 9] + 0x5a827999, 15) + d; b = RL(b, 10); + d = RL(d + f2(e,a,b) + X[ 5] + 0x5a827999, 9) + c; a = RL(a, 10); + c = RL(c + f2(d,e,a) + X[ 2] + 0x5a827999, 11) + b; e = RL(e, 10); + b = RL(b + f2(c,d,e) + X[14] + 0x5a827999, 7) + a; d = RL(d, 10); + a = RL(a + f2(b,c,d) + X[11] + 0x5a827999, 13) + e; c = RL(c, 10); + e = RL(e + f2(a,b,c) + X[ 8] + 0x5a827999, 12) + d; b = RL(b, 10); + + // right + ee = RL(ee + f4(aa,bb,cc) + X[ 6] + 0x5c4dd124, 9) + dd; bb = RL(bb, 10); + dd = RL(dd + f4(ee,aa,bb) + X[11] + 0x5c4dd124, 13) + cc; aa = RL(aa, 10); + cc = RL(cc + f4(dd,ee,aa) + X[ 3] + 0x5c4dd124, 15) + bb; ee = RL(ee, 10); + bb = RL(bb + f4(cc,dd,ee) + X[ 7] + 0x5c4dd124, 7) + aa; dd = RL(dd, 10); + aa = RL(aa + f4(bb,cc,dd) + X[ 0] + 0x5c4dd124, 12) + ee; cc = RL(cc, 10); + ee = RL(ee + f4(aa,bb,cc) + X[13] + 0x5c4dd124, 8) + dd; bb = RL(bb, 10); + dd = RL(dd + f4(ee,aa,bb) + X[ 5] + 0x5c4dd124, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f4(dd,ee,aa) + X[10] + 0x5c4dd124, 11) + bb; ee = RL(ee, 10); + bb = RL(bb + f4(cc,dd,ee) + X[14] + 0x5c4dd124, 7) + aa; dd = RL(dd, 10); + aa = RL(aa + f4(bb,cc,dd) + X[15] + 0x5c4dd124, 7) + ee; cc = RL(cc, 10); + ee = RL(ee + f4(aa,bb,cc) + X[ 8] + 0x5c4dd124, 12) + dd; bb = RL(bb, 10); + dd = RL(dd + f4(ee,aa,bb) + X[12] + 0x5c4dd124, 7) + cc; aa = RL(aa, 10); + cc = RL(cc + f4(dd,ee,aa) + X[ 4] + 0x5c4dd124, 6) + bb; ee = RL(ee, 10); + bb = RL(bb + f4(cc,dd,ee) + X[ 9] + 0x5c4dd124, 15) + aa; dd = RL(dd, 10); + aa = RL(aa + f4(bb,cc,dd) + X[ 1] + 0x5c4dd124, 13) + ee; cc = RL(cc, 10); + ee = RL(ee + f4(aa,bb,cc) + X[ 2] + 0x5c4dd124, 11) + dd; bb = RL(bb, 10); + + // + // Rounds 32-47 + // + // left + d = RL(d + f3(e,a,b) + X[ 3] + 0x6ed9eba1, 11) + c; a = RL(a, 10); + c = RL(c + f3(d,e,a) + X[10] + 0x6ed9eba1, 13) + b; e = RL(e, 10); + b = RL(b + f3(c,d,e) + X[14] + 0x6ed9eba1, 6) + a; d = RL(d, 10); + a = RL(a + f3(b,c,d) + X[ 4] + 0x6ed9eba1, 7) + e; c = RL(c, 10); + e = RL(e + f3(a,b,c) + X[ 9] + 0x6ed9eba1, 14) + d; b = RL(b, 10); + d = RL(d + f3(e,a,b) + X[15] + 0x6ed9eba1, 9) + c; a = RL(a, 10); + c = RL(c + f3(d,e,a) + X[ 8] + 0x6ed9eba1, 13) + b; e = RL(e, 10); + b = RL(b + f3(c,d,e) + X[ 1] + 0x6ed9eba1, 15) + a; d = RL(d, 10); + a = RL(a + f3(b,c,d) + X[ 2] + 0x6ed9eba1, 14) + e; c = RL(c, 10); + e = RL(e + f3(a,b,c) + X[ 7] + 0x6ed9eba1, 8) + d; b = RL(b, 10); + d = RL(d + f3(e,a,b) + X[ 0] + 0x6ed9eba1, 13) + c; a = RL(a, 10); + c = RL(c + f3(d,e,a) + X[ 6] + 0x6ed9eba1, 6) + b; e = RL(e, 10); + b = RL(b + f3(c,d,e) + X[13] + 0x6ed9eba1, 5) + a; d = RL(d, 10); + a = RL(a + f3(b,c,d) + X[11] + 0x6ed9eba1, 12) + e; c = RL(c, 10); + e = RL(e + f3(a,b,c) + X[ 5] + 0x6ed9eba1, 7) + d; b = RL(b, 10); + d = RL(d + f3(e,a,b) + X[12] + 0x6ed9eba1, 5) + c; a = RL(a, 10); + + // right + dd = RL(dd + f3(ee,aa,bb) + X[15] + 0x6d703ef3, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f3(dd,ee,aa) + X[ 5] + 0x6d703ef3, 7) + bb; ee = RL(ee, 10); + bb = RL(bb + f3(cc,dd,ee) + X[ 1] + 0x6d703ef3, 15) + aa; dd = RL(dd, 10); + aa = RL(aa + f3(bb,cc,dd) + X[ 3] + 0x6d703ef3, 11) + ee; cc = RL(cc, 10); + ee = RL(ee + f3(aa,bb,cc) + X[ 7] + 0x6d703ef3, 8) + dd; bb = RL(bb, 10); + dd = RL(dd + f3(ee,aa,bb) + X[14] + 0x6d703ef3, 6) + cc; aa = RL(aa, 10); + cc = RL(cc + f3(dd,ee,aa) + X[ 6] + 0x6d703ef3, 6) + bb; ee = RL(ee, 10); + bb = RL(bb + f3(cc,dd,ee) + X[ 9] + 0x6d703ef3, 14) + aa; dd = RL(dd, 10); + aa = RL(aa + f3(bb,cc,dd) + X[11] + 0x6d703ef3, 12) + ee; cc = RL(cc, 10); + ee = RL(ee + f3(aa,bb,cc) + X[ 8] + 0x6d703ef3, 13) + dd; bb = RL(bb, 10); + dd = RL(dd + f3(ee,aa,bb) + X[12] + 0x6d703ef3, 5) + cc; aa = RL(aa, 10); + cc = RL(cc + f3(dd,ee,aa) + X[ 2] + 0x6d703ef3, 14) + bb; ee = RL(ee, 10); + bb = RL(bb + f3(cc,dd,ee) + X[10] + 0x6d703ef3, 13) + aa; dd = RL(dd, 10); + aa = RL(aa + f3(bb,cc,dd) + X[ 0] + 0x6d703ef3, 13) + ee; cc = RL(cc, 10); + ee = RL(ee + f3(aa,bb,cc) + X[ 4] + 0x6d703ef3, 7) + dd; bb = RL(bb, 10); + dd = RL(dd + f3(ee,aa,bb) + X[13] + 0x6d703ef3, 5) + cc; aa = RL(aa, 10); + + // + // Rounds 48-63 + // + // left + c = RL(c + f4(d,e,a) + X[ 1] + 0x8f1bbcdc, 11) + b; e = RL(e, 10); + b = RL(b + f4(c,d,e) + X[ 9] + 0x8f1bbcdc, 12) + a; d = RL(d, 10); + a = RL(a + f4(b,c,d) + X[11] + 0x8f1bbcdc, 14) + e; c = RL(c, 10); + e = RL(e + f4(a,b,c) + X[10] + 0x8f1bbcdc, 15) + d; b = RL(b, 10); + d = RL(d + f4(e,a,b) + X[ 0] + 0x8f1bbcdc, 14) + c; a = RL(a, 10); + c = RL(c + f4(d,e,a) + X[ 8] + 0x8f1bbcdc, 15) + b; e = RL(e, 10); + b = RL(b + f4(c,d,e) + X[12] + 0x8f1bbcdc, 9) + a; d = RL(d, 10); + a = RL(a + f4(b,c,d) + X[ 4] + 0x8f1bbcdc, 8) + e; c = RL(c, 10); + e = RL(e + f4(a,b,c) + X[13] + 0x8f1bbcdc, 9) + d; b = RL(b, 10); + d = RL(d + f4(e,a,b) + X[ 3] + 0x8f1bbcdc, 14) + c; a = RL(a, 10); + c = RL(c + f4(d,e,a) + X[ 7] + 0x8f1bbcdc, 5) + b; e = RL(e, 10); + b = RL(b + f4(c,d,e) + X[15] + 0x8f1bbcdc, 6) + a; d = RL(d, 10); + a = RL(a + f4(b,c,d) + X[14] + 0x8f1bbcdc, 8) + e; c = RL(c, 10); + e = RL(e + f4(a,b,c) + X[ 5] + 0x8f1bbcdc, 6) + d; b = RL(b, 10); + d = RL(d + f4(e,a,b) + X[ 6] + 0x8f1bbcdc, 5) + c; a = RL(a, 10); + c = RL(c + f4(d,e,a) + X[ 2] + 0x8f1bbcdc, 12) + b; e = RL(e, 10); + + // right + cc = RL(cc + f2(dd,ee,aa) + X[ 8] + 0x7a6d76e9, 15) + bb; ee = RL(ee, 10); + bb = RL(bb + f2(cc,dd,ee) + X[ 6] + 0x7a6d76e9, 5) + aa; dd = RL(dd, 10); + aa = RL(aa + f2(bb,cc,dd) + X[ 4] + 0x7a6d76e9, 8) + ee; cc = RL(cc, 10); + ee = RL(ee + f2(aa,bb,cc) + X[ 1] + 0x7a6d76e9, 11) + dd; bb = RL(bb, 10); + dd = RL(dd + f2(ee,aa,bb) + X[ 3] + 0x7a6d76e9, 14) + cc; aa = RL(aa, 10); + cc = RL(cc + f2(dd,ee,aa) + X[11] + 0x7a6d76e9, 14) + bb; ee = RL(ee, 10); + bb = RL(bb + f2(cc,dd,ee) + X[15] + 0x7a6d76e9, 6) + aa; dd = RL(dd, 10); + aa = RL(aa + f2(bb,cc,dd) + X[ 0] + 0x7a6d76e9, 14) + ee; cc = RL(cc, 10); + ee = RL(ee + f2(aa,bb,cc) + X[ 5] + 0x7a6d76e9, 6) + dd; bb = RL(bb, 10); + dd = RL(dd + f2(ee,aa,bb) + X[12] + 0x7a6d76e9, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f2(dd,ee,aa) + X[ 2] + 0x7a6d76e9, 12) + bb; ee = RL(ee, 10); + bb = RL(bb + f2(cc,dd,ee) + X[13] + 0x7a6d76e9, 9) + aa; dd = RL(dd, 10); + aa = RL(aa + f2(bb,cc,dd) + X[ 9] + 0x7a6d76e9, 12) + ee; cc = RL(cc, 10); + ee = RL(ee + f2(aa,bb,cc) + X[ 7] + 0x7a6d76e9, 5) + dd; bb = RL(bb, 10); + dd = RL(dd + f2(ee,aa,bb) + X[10] + 0x7a6d76e9, 15) + cc; aa = RL(aa, 10); + cc = RL(cc + f2(dd,ee,aa) + X[14] + 0x7a6d76e9, 8) + bb; ee = RL(ee, 10); + + // + // Rounds 64-79 + // + // left + b = RL(b + f5(c,d,e) + X[ 4] + 0xa953fd4e, 9) + a; d = RL(d, 10); + a = RL(a + f5(b,c,d) + X[ 0] + 0xa953fd4e, 15) + e; c = RL(c, 10); + e = RL(e + f5(a,b,c) + X[ 5] + 0xa953fd4e, 5) + d; b = RL(b, 10); + d = RL(d + f5(e,a,b) + X[ 9] + 0xa953fd4e, 11) + c; a = RL(a, 10); + c = RL(c + f5(d,e,a) + X[ 7] + 0xa953fd4e, 6) + b; e = RL(e, 10); + b = RL(b + f5(c,d,e) + X[12] + 0xa953fd4e, 8) + a; d = RL(d, 10); + a = RL(a + f5(b,c,d) + X[ 2] + 0xa953fd4e, 13) + e; c = RL(c, 10); + e = RL(e + f5(a,b,c) + X[10] + 0xa953fd4e, 12) + d; b = RL(b, 10); + d = RL(d + f5(e,a,b) + X[14] + 0xa953fd4e, 5) + c; a = RL(a, 10); + c = RL(c + f5(d,e,a) + X[ 1] + 0xa953fd4e, 12) + b; e = RL(e, 10); + b = RL(b + f5(c,d,e) + X[ 3] + 0xa953fd4e, 13) + a; d = RL(d, 10); + a = RL(a + f5(b,c,d) + X[ 8] + 0xa953fd4e, 14) + e; c = RL(c, 10); + e = RL(e + f5(a,b,c) + X[11] + 0xa953fd4e, 11) + d; b = RL(b, 10); + d = RL(d + f5(e,a,b) + X[ 6] + 0xa953fd4e, 8) + c; a = RL(a, 10); + c = RL(c + f5(d,e,a) + X[15] + 0xa953fd4e, 5) + b; e = RL(e, 10); + b = RL(b + f5(c,d,e) + X[13] + 0xa953fd4e, 6) + a; d = RL(d, 10); + + // right + bb = RL(bb + f1(cc,dd,ee) + X[12], 8) + aa; dd = RL(dd, 10); + aa = RL(aa + f1(bb,cc,dd) + X[15], 5) + ee; cc = RL(cc, 10); + ee = RL(ee + f1(aa,bb,cc) + X[10], 12) + dd; bb = RL(bb, 10); + dd = RL(dd + f1(ee,aa,bb) + X[ 4], 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f1(dd,ee,aa) + X[ 1], 12) + bb; ee = RL(ee, 10); + bb = RL(bb + f1(cc,dd,ee) + X[ 5], 5) + aa; dd = RL(dd, 10); + aa = RL(aa + f1(bb,cc,dd) + X[ 8], 14) + ee; cc = RL(cc, 10); + ee = RL(ee + f1(aa,bb,cc) + X[ 7], 6) + dd; bb = RL(bb, 10); + dd = RL(dd + f1(ee,aa,bb) + X[ 6], 8) + cc; aa = RL(aa, 10); + cc = RL(cc + f1(dd,ee,aa) + X[ 2], 13) + bb; ee = RL(ee, 10); + bb = RL(bb + f1(cc,dd,ee) + X[13], 6) + aa; dd = RL(dd, 10); + aa = RL(aa + f1(bb,cc,dd) + X[14], 5) + ee; cc = RL(cc, 10); + ee = RL(ee + f1(aa,bb,cc) + X[ 0], 15) + dd; bb = RL(bb, 10); + dd = RL(dd + f1(ee,aa,bb) + X[ 3], 13) + cc; aa = RL(aa, 10); + cc = RL(cc + f1(dd,ee,aa) + X[ 9], 11) + bb; ee = RL(ee, 10); + bb = RL(bb + f1(cc,dd,ee) + X[11], 11) + aa; dd = RL(dd, 10); + + dd += c + H1; + H1 = H2 + d + ee; + H2 = H3 + e + aa; + H3 = H4 + a + bb; + H4 = H0 + b + cc; + H0 = dd; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD256Digest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD256Digest.java new file mode 100644 index 000000000..001a4907f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD256Digest.java @@ -0,0 +1,476 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + + +/** + * implementation of RIPEMD256. + *

+ * note: this algorithm offers the same level of security as RIPEMD128. + */ +public class RIPEMD256Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 32; + + private int H0, H1, H2, H3, H4, H5, H6, H7; // IV's + + private int[] X = new int[16]; + private int xOff; + + /** + * Standard constructor + */ + public RIPEMD256Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public RIPEMD256Digest(RIPEMD256Digest t) + { + super(t); + + H0 = t.H0; + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + H5 = t.H5; + H6 = t.H6; + H7 = t.H7; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "RIPEMD256"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) + | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)(bitLength >>> 32); + } + + private void unpackWord( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)word; + out[outOff + 1] = (byte)(word >>> 8); + out[outOff + 2] = (byte)(word >>> 16); + out[outOff + 3] = (byte)(word >>> 24); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(H0, out, outOff); + unpackWord(H1, out, outOff + 4); + unpackWord(H2, out, outOff + 8); + unpackWord(H3, out, outOff + 12); + unpackWord(H4, out, outOff + 16); + unpackWord(H5, out, outOff + 20); + unpackWord(H6, out, outOff + 24); + unpackWord(H7, out, outOff + 28); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + public void reset() + { + super.reset(); + + H0 = 0x67452301; + H1 = 0xefcdab89; + H2 = 0x98badcfe; + H3 = 0x10325476; + H4 = 0x76543210; + H5 = 0xFEDCBA98; + H6 = 0x89ABCDEF; + H7 = 0x01234567; + + xOff = 0; + + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + /* + * rotate int x left n bits. + */ + private int RL( + int x, + int n) + { + return (x << n) | (x >>> (32 - n)); + } + + /* + * f1,f2,f3,f4 are the basic RIPEMD128 functions. + */ + + /* + * F + */ + private int f1( + int x, + int y, + int z) + { + return x ^ y ^ z; + } + + /* + * G + */ + private int f2( + int x, + int y, + int z) + { + return (x & y) | (~x & z); + } + + /* + * H + */ + private int f3( + int x, + int y, + int z) + { + return (x | ~y) ^ z; + } + + /* + * I + */ + private int f4( + int x, + int y, + int z) + { + return (x & z) | (y & ~z); + } + + private int F1( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f1(b, c, d) + x, s); + } + + private int F2( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f2(b, c, d) + x + 0x5a827999, s); + } + + private int F3( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f3(b, c, d) + x + 0x6ed9eba1, s); + } + + private int F4( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f4(b, c, d) + x + 0x8f1bbcdc, s); + } + + private int FF1( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f1(b, c, d) + x, s); + } + + private int FF2( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f2(b, c, d) + x + 0x6d703ef3, s); + } + + private int FF3( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f3(b, c, d) + x + 0x5c4dd124, s); + } + + private int FF4( + int a, + int b, + int c, + int d, + int x, + int s) + { + return RL(a + f4(b, c, d) + x + 0x50a28be6, s); + } + + protected void processBlock() + { + int a, aa; + int b, bb; + int c, cc; + int d, dd; + int t; + + a = H0; + b = H1; + c = H2; + d = H3; + aa = H4; + bb = H5; + cc = H6; + dd = H7; + + // + // Round 1 + // + + a = F1(a, b, c, d, X[ 0], 11); + d = F1(d, a, b, c, X[ 1], 14); + c = F1(c, d, a, b, X[ 2], 15); + b = F1(b, c, d, a, X[ 3], 12); + a = F1(a, b, c, d, X[ 4], 5); + d = F1(d, a, b, c, X[ 5], 8); + c = F1(c, d, a, b, X[ 6], 7); + b = F1(b, c, d, a, X[ 7], 9); + a = F1(a, b, c, d, X[ 8], 11); + d = F1(d, a, b, c, X[ 9], 13); + c = F1(c, d, a, b, X[10], 14); + b = F1(b, c, d, a, X[11], 15); + a = F1(a, b, c, d, X[12], 6); + d = F1(d, a, b, c, X[13], 7); + c = F1(c, d, a, b, X[14], 9); + b = F1(b, c, d, a, X[15], 8); + + aa = FF4(aa, bb, cc, dd, X[ 5], 8); + dd = FF4(dd, aa, bb, cc, X[14], 9); + cc = FF4(cc, dd, aa, bb, X[ 7], 9); + bb = FF4(bb, cc, dd, aa, X[ 0], 11); + aa = FF4(aa, bb, cc, dd, X[ 9], 13); + dd = FF4(dd, aa, bb, cc, X[ 2], 15); + cc = FF4(cc, dd, aa, bb, X[11], 15); + bb = FF4(bb, cc, dd, aa, X[ 4], 5); + aa = FF4(aa, bb, cc, dd, X[13], 7); + dd = FF4(dd, aa, bb, cc, X[ 6], 7); + cc = FF4(cc, dd, aa, bb, X[15], 8); + bb = FF4(bb, cc, dd, aa, X[ 8], 11); + aa = FF4(aa, bb, cc, dd, X[ 1], 14); + dd = FF4(dd, aa, bb, cc, X[10], 14); + cc = FF4(cc, dd, aa, bb, X[ 3], 12); + bb = FF4(bb, cc, dd, aa, X[12], 6); + + t = a; a = aa; aa = t; + + // + // Round 2 + // + a = F2(a, b, c, d, X[ 7], 7); + d = F2(d, a, b, c, X[ 4], 6); + c = F2(c, d, a, b, X[13], 8); + b = F2(b, c, d, a, X[ 1], 13); + a = F2(a, b, c, d, X[10], 11); + d = F2(d, a, b, c, X[ 6], 9); + c = F2(c, d, a, b, X[15], 7); + b = F2(b, c, d, a, X[ 3], 15); + a = F2(a, b, c, d, X[12], 7); + d = F2(d, a, b, c, X[ 0], 12); + c = F2(c, d, a, b, X[ 9], 15); + b = F2(b, c, d, a, X[ 5], 9); + a = F2(a, b, c, d, X[ 2], 11); + d = F2(d, a, b, c, X[14], 7); + c = F2(c, d, a, b, X[11], 13); + b = F2(b, c, d, a, X[ 8], 12); + + aa = FF3(aa, bb, cc, dd, X[ 6], 9); + dd = FF3(dd, aa, bb, cc, X[ 11], 13); + cc = FF3(cc, dd, aa, bb, X[3], 15); + bb = FF3(bb, cc, dd, aa, X[ 7], 7); + aa = FF3(aa, bb, cc, dd, X[0], 12); + dd = FF3(dd, aa, bb, cc, X[13], 8); + cc = FF3(cc, dd, aa, bb, X[5], 9); + bb = FF3(bb, cc, dd, aa, X[10], 11); + aa = FF3(aa, bb, cc, dd, X[14], 7); + dd = FF3(dd, aa, bb, cc, X[15], 7); + cc = FF3(cc, dd, aa, bb, X[ 8], 12); + bb = FF3(bb, cc, dd, aa, X[12], 7); + aa = FF3(aa, bb, cc, dd, X[ 4], 6); + dd = FF3(dd, aa, bb, cc, X[ 9], 15); + cc = FF3(cc, dd, aa, bb, X[ 1], 13); + bb = FF3(bb, cc, dd, aa, X[ 2], 11); + + t = b; b = bb; bb = t; + + // + // Round 3 + // + a = F3(a, b, c, d, X[ 3], 11); + d = F3(d, a, b, c, X[10], 13); + c = F3(c, d, a, b, X[14], 6); + b = F3(b, c, d, a, X[ 4], 7); + a = F3(a, b, c, d, X[ 9], 14); + d = F3(d, a, b, c, X[15], 9); + c = F3(c, d, a, b, X[ 8], 13); + b = F3(b, c, d, a, X[ 1], 15); + a = F3(a, b, c, d, X[ 2], 14); + d = F3(d, a, b, c, X[ 7], 8); + c = F3(c, d, a, b, X[ 0], 13); + b = F3(b, c, d, a, X[ 6], 6); + a = F3(a, b, c, d, X[13], 5); + d = F3(d, a, b, c, X[11], 12); + c = F3(c, d, a, b, X[ 5], 7); + b = F3(b, c, d, a, X[12], 5); + + aa = FF2(aa, bb, cc, dd, X[ 15], 9); + dd = FF2(dd, aa, bb, cc, X[5], 7); + cc = FF2(cc, dd, aa, bb, X[1], 15); + bb = FF2(bb, cc, dd, aa, X[ 3], 11); + aa = FF2(aa, bb, cc, dd, X[ 7], 8); + dd = FF2(dd, aa, bb, cc, X[14], 6); + cc = FF2(cc, dd, aa, bb, X[ 6], 6); + bb = FF2(bb, cc, dd, aa, X[ 9], 14); + aa = FF2(aa, bb, cc, dd, X[11], 12); + dd = FF2(dd, aa, bb, cc, X[ 8], 13); + cc = FF2(cc, dd, aa, bb, X[12], 5); + bb = FF2(bb, cc, dd, aa, X[ 2], 14); + aa = FF2(aa, bb, cc, dd, X[10], 13); + dd = FF2(dd, aa, bb, cc, X[ 0], 13); + cc = FF2(cc, dd, aa, bb, X[ 4], 7); + bb = FF2(bb, cc, dd, aa, X[13], 5); + + t = c; c = cc; cc = t; + + // + // Round 4 + // + a = F4(a, b, c, d, X[ 1], 11); + d = F4(d, a, b, c, X[ 9], 12); + c = F4(c, d, a, b, X[11], 14); + b = F4(b, c, d, a, X[10], 15); + a = F4(a, b, c, d, X[ 0], 14); + d = F4(d, a, b, c, X[ 8], 15); + c = F4(c, d, a, b, X[12], 9); + b = F4(b, c, d, a, X[ 4], 8); + a = F4(a, b, c, d, X[13], 9); + d = F4(d, a, b, c, X[ 3], 14); + c = F4(c, d, a, b, X[ 7], 5); + b = F4(b, c, d, a, X[15], 6); + a = F4(a, b, c, d, X[14], 8); + d = F4(d, a, b, c, X[ 5], 6); + c = F4(c, d, a, b, X[ 6], 5); + b = F4(b, c, d, a, X[ 2], 12); + + aa = FF1(aa, bb, cc, dd, X[ 8], 15); + dd = FF1(dd, aa, bb, cc, X[ 6], 5); + cc = FF1(cc, dd, aa, bb, X[ 4], 8); + bb = FF1(bb, cc, dd, aa, X[ 1], 11); + aa = FF1(aa, bb, cc, dd, X[ 3], 14); + dd = FF1(dd, aa, bb, cc, X[11], 14); + cc = FF1(cc, dd, aa, bb, X[15], 6); + bb = FF1(bb, cc, dd, aa, X[ 0], 14); + aa = FF1(aa, bb, cc, dd, X[ 5], 6); + dd = FF1(dd, aa, bb, cc, X[12], 9); + cc = FF1(cc, dd, aa, bb, X[ 2], 12); + bb = FF1(bb, cc, dd, aa, X[13], 9); + aa = FF1(aa, bb, cc, dd, X[ 9], 12); + dd = FF1(dd, aa, bb, cc, X[ 7], 5); + cc = FF1(cc, dd, aa, bb, X[10], 15); + bb = FF1(bb, cc, dd, aa, X[14], 8); + + t = d; d = dd; dd = t; + + H0 += a; + H1 += b; + H2 += c; + H3 += d; + H4 += aa; + H5 += bb; + H6 += cc; + H7 += dd; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD320Digest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD320Digest.java new file mode 100644 index 000000000..d617c7600 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/RIPEMD320Digest.java @@ -0,0 +1,461 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + + +/** + * implementation of RIPEMD 320. + *

+ * Note: this implementation offers the same level of security + * as RIPEMD 160. + */ +public class RIPEMD320Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 40; + + private int H0, H1, H2, H3, H4, H5, H6, H7, H8, H9; // IV's + + private int[] X = new int[16]; + private int xOff; + + /** + * Standard constructor + */ + public RIPEMD320Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public RIPEMD320Digest(RIPEMD320Digest t) + { + super(t); + + H0 = t.H0; + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + H5 = t.H5; + H6 = t.H6; + H7 = t.H7; + H8 = t.H8; + H9 = t.H9; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "RIPEMD320"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) + | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)(bitLength >>> 32); + } + + private void unpackWord( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)word; + out[outOff + 1] = (byte)(word >>> 8); + out[outOff + 2] = (byte)(word >>> 16); + out[outOff + 3] = (byte)(word >>> 24); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(H0, out, outOff); + unpackWord(H1, out, outOff + 4); + unpackWord(H2, out, outOff + 8); + unpackWord(H3, out, outOff + 12); + unpackWord(H4, out, outOff + 16); + unpackWord(H5, out, outOff + 20); + unpackWord(H6, out, outOff + 24); + unpackWord(H7, out, outOff + 28); + unpackWord(H8, out, outOff + 32); + unpackWord(H9, out, outOff + 36); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + public void reset() + { + super.reset(); + + H0 = 0x67452301; + H1 = 0xefcdab89; + H2 = 0x98badcfe; + H3 = 0x10325476; + H4 = 0xc3d2e1f0; + H5 = 0x76543210; + H6 = 0xFEDCBA98; + H7 = 0x89ABCDEF; + H8 = 0x01234567; + H9 = 0x3C2D1E0F; + + xOff = 0; + + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + /* + * rotate int x left n bits. + */ + private int RL( + int x, + int n) + { + return (x << n) | (x >>> (32 - n)); + } + + /* + * f1,f2,f3,f4,f5 are the basic RIPEMD160 functions. + */ + + /* + * rounds 0-15 + */ + private int f1( + int x, + int y, + int z) + { + return x ^ y ^ z; + } + + /* + * rounds 16-31 + */ + private int f2( + int x, + int y, + int z) + { + return (x & y) | (~x & z); + } + + /* + * rounds 32-47 + */ + private int f3( + int x, + int y, + int z) + { + return (x | ~y) ^ z; + } + + /* + * rounds 48-63 + */ + private int f4( + int x, + int y, + int z) + { + return (x & z) | (y & ~z); + } + + /* + * rounds 64-79 + */ + private int f5( + int x, + int y, + int z) + { + return x ^ (y | ~z); + } + + protected void processBlock() + { + int a, aa; + int b, bb; + int c, cc; + int d, dd; + int e, ee; + int t; + + a = H0; + b = H1; + c = H2; + d = H3; + e = H4; + aa = H5; + bb = H6; + cc = H7; + dd = H8; + ee = H9; + + // + // Rounds 1 - 16 + // + // left + a = RL(a + f1(b,c,d) + X[ 0], 11) + e; c = RL(c, 10); + e = RL(e + f1(a,b,c) + X[ 1], 14) + d; b = RL(b, 10); + d = RL(d + f1(e,a,b) + X[ 2], 15) + c; a = RL(a, 10); + c = RL(c + f1(d,e,a) + X[ 3], 12) + b; e = RL(e, 10); + b = RL(b + f1(c,d,e) + X[ 4], 5) + a; d = RL(d, 10); + a = RL(a + f1(b,c,d) + X[ 5], 8) + e; c = RL(c, 10); + e = RL(e + f1(a,b,c) + X[ 6], 7) + d; b = RL(b, 10); + d = RL(d + f1(e,a,b) + X[ 7], 9) + c; a = RL(a, 10); + c = RL(c + f1(d,e,a) + X[ 8], 11) + b; e = RL(e, 10); + b = RL(b + f1(c,d,e) + X[ 9], 13) + a; d = RL(d, 10); + a = RL(a + f1(b,c,d) + X[10], 14) + e; c = RL(c, 10); + e = RL(e + f1(a,b,c) + X[11], 15) + d; b = RL(b, 10); + d = RL(d + f1(e,a,b) + X[12], 6) + c; a = RL(a, 10); + c = RL(c + f1(d,e,a) + X[13], 7) + b; e = RL(e, 10); + b = RL(b + f1(c,d,e) + X[14], 9) + a; d = RL(d, 10); + a = RL(a + f1(b,c,d) + X[15], 8) + e; c = RL(c, 10); + + // right + aa = RL(aa + f5(bb,cc,dd) + X[ 5] + 0x50a28be6, 8) + ee; cc = RL(cc, 10); + ee = RL(ee + f5(aa,bb,cc) + X[14] + 0x50a28be6, 9) + dd; bb = RL(bb, 10); + dd = RL(dd + f5(ee,aa,bb) + X[ 7] + 0x50a28be6, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f5(dd,ee,aa) + X[ 0] + 0x50a28be6, 11) + bb; ee = RL(ee, 10); + bb = RL(bb + f5(cc,dd,ee) + X[ 9] + 0x50a28be6, 13) + aa; dd = RL(dd, 10); + aa = RL(aa + f5(bb,cc,dd) + X[ 2] + 0x50a28be6, 15) + ee; cc = RL(cc, 10); + ee = RL(ee + f5(aa,bb,cc) + X[11] + 0x50a28be6, 15) + dd; bb = RL(bb, 10); + dd = RL(dd + f5(ee,aa,bb) + X[ 4] + 0x50a28be6, 5) + cc; aa = RL(aa, 10); + cc = RL(cc + f5(dd,ee,aa) + X[13] + 0x50a28be6, 7) + bb; ee = RL(ee, 10); + bb = RL(bb + f5(cc,dd,ee) + X[ 6] + 0x50a28be6, 7) + aa; dd = RL(dd, 10); + aa = RL(aa + f5(bb,cc,dd) + X[15] + 0x50a28be6, 8) + ee; cc = RL(cc, 10); + ee = RL(ee + f5(aa,bb,cc) + X[ 8] + 0x50a28be6, 11) + dd; bb = RL(bb, 10); + dd = RL(dd + f5(ee,aa,bb) + X[ 1] + 0x50a28be6, 14) + cc; aa = RL(aa, 10); + cc = RL(cc + f5(dd,ee,aa) + X[10] + 0x50a28be6, 14) + bb; ee = RL(ee, 10); + bb = RL(bb + f5(cc,dd,ee) + X[ 3] + 0x50a28be6, 12) + aa; dd = RL(dd, 10); + aa = RL(aa + f5(bb,cc,dd) + X[12] + 0x50a28be6, 6) + ee; cc = RL(cc, 10); + + t = a; a = aa; aa = t; + + // + // Rounds 16-31 + // + // left + e = RL(e + f2(a,b,c) + X[ 7] + 0x5a827999, 7) + d; b = RL(b, 10); + d = RL(d + f2(e,a,b) + X[ 4] + 0x5a827999, 6) + c; a = RL(a, 10); + c = RL(c + f2(d,e,a) + X[13] + 0x5a827999, 8) + b; e = RL(e, 10); + b = RL(b + f2(c,d,e) + X[ 1] + 0x5a827999, 13) + a; d = RL(d, 10); + a = RL(a + f2(b,c,d) + X[10] + 0x5a827999, 11) + e; c = RL(c, 10); + e = RL(e + f2(a,b,c) + X[ 6] + 0x5a827999, 9) + d; b = RL(b, 10); + d = RL(d + f2(e,a,b) + X[15] + 0x5a827999, 7) + c; a = RL(a, 10); + c = RL(c + f2(d,e,a) + X[ 3] + 0x5a827999, 15) + b; e = RL(e, 10); + b = RL(b + f2(c,d,e) + X[12] + 0x5a827999, 7) + a; d = RL(d, 10); + a = RL(a + f2(b,c,d) + X[ 0] + 0x5a827999, 12) + e; c = RL(c, 10); + e = RL(e + f2(a,b,c) + X[ 9] + 0x5a827999, 15) + d; b = RL(b, 10); + d = RL(d + f2(e,a,b) + X[ 5] + 0x5a827999, 9) + c; a = RL(a, 10); + c = RL(c + f2(d,e,a) + X[ 2] + 0x5a827999, 11) + b; e = RL(e, 10); + b = RL(b + f2(c,d,e) + X[14] + 0x5a827999, 7) + a; d = RL(d, 10); + a = RL(a + f2(b,c,d) + X[11] + 0x5a827999, 13) + e; c = RL(c, 10); + e = RL(e + f2(a,b,c) + X[ 8] + 0x5a827999, 12) + d; b = RL(b, 10); + + // right + ee = RL(ee + f4(aa,bb,cc) + X[ 6] + 0x5c4dd124, 9) + dd; bb = RL(bb, 10); + dd = RL(dd + f4(ee,aa,bb) + X[11] + 0x5c4dd124, 13) + cc; aa = RL(aa, 10); + cc = RL(cc + f4(dd,ee,aa) + X[ 3] + 0x5c4dd124, 15) + bb; ee = RL(ee, 10); + bb = RL(bb + f4(cc,dd,ee) + X[ 7] + 0x5c4dd124, 7) + aa; dd = RL(dd, 10); + aa = RL(aa + f4(bb,cc,dd) + X[ 0] + 0x5c4dd124, 12) + ee; cc = RL(cc, 10); + ee = RL(ee + f4(aa,bb,cc) + X[13] + 0x5c4dd124, 8) + dd; bb = RL(bb, 10); + dd = RL(dd + f4(ee,aa,bb) + X[ 5] + 0x5c4dd124, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f4(dd,ee,aa) + X[10] + 0x5c4dd124, 11) + bb; ee = RL(ee, 10); + bb = RL(bb + f4(cc,dd,ee) + X[14] + 0x5c4dd124, 7) + aa; dd = RL(dd, 10); + aa = RL(aa + f4(bb,cc,dd) + X[15] + 0x5c4dd124, 7) + ee; cc = RL(cc, 10); + ee = RL(ee + f4(aa,bb,cc) + X[ 8] + 0x5c4dd124, 12) + dd; bb = RL(bb, 10); + dd = RL(dd + f4(ee,aa,bb) + X[12] + 0x5c4dd124, 7) + cc; aa = RL(aa, 10); + cc = RL(cc + f4(dd,ee,aa) + X[ 4] + 0x5c4dd124, 6) + bb; ee = RL(ee, 10); + bb = RL(bb + f4(cc,dd,ee) + X[ 9] + 0x5c4dd124, 15) + aa; dd = RL(dd, 10); + aa = RL(aa + f4(bb,cc,dd) + X[ 1] + 0x5c4dd124, 13) + ee; cc = RL(cc, 10); + ee = RL(ee + f4(aa,bb,cc) + X[ 2] + 0x5c4dd124, 11) + dd; bb = RL(bb, 10); + + t = b; b = bb; bb = t; + + // + // Rounds 32-47 + // + // left + d = RL(d + f3(e,a,b) + X[ 3] + 0x6ed9eba1, 11) + c; a = RL(a, 10); + c = RL(c + f3(d,e,a) + X[10] + 0x6ed9eba1, 13) + b; e = RL(e, 10); + b = RL(b + f3(c,d,e) + X[14] + 0x6ed9eba1, 6) + a; d = RL(d, 10); + a = RL(a + f3(b,c,d) + X[ 4] + 0x6ed9eba1, 7) + e; c = RL(c, 10); + e = RL(e + f3(a,b,c) + X[ 9] + 0x6ed9eba1, 14) + d; b = RL(b, 10); + d = RL(d + f3(e,a,b) + X[15] + 0x6ed9eba1, 9) + c; a = RL(a, 10); + c = RL(c + f3(d,e,a) + X[ 8] + 0x6ed9eba1, 13) + b; e = RL(e, 10); + b = RL(b + f3(c,d,e) + X[ 1] + 0x6ed9eba1, 15) + a; d = RL(d, 10); + a = RL(a + f3(b,c,d) + X[ 2] + 0x6ed9eba1, 14) + e; c = RL(c, 10); + e = RL(e + f3(a,b,c) + X[ 7] + 0x6ed9eba1, 8) + d; b = RL(b, 10); + d = RL(d + f3(e,a,b) + X[ 0] + 0x6ed9eba1, 13) + c; a = RL(a, 10); + c = RL(c + f3(d,e,a) + X[ 6] + 0x6ed9eba1, 6) + b; e = RL(e, 10); + b = RL(b + f3(c,d,e) + X[13] + 0x6ed9eba1, 5) + a; d = RL(d, 10); + a = RL(a + f3(b,c,d) + X[11] + 0x6ed9eba1, 12) + e; c = RL(c, 10); + e = RL(e + f3(a,b,c) + X[ 5] + 0x6ed9eba1, 7) + d; b = RL(b, 10); + d = RL(d + f3(e,a,b) + X[12] + 0x6ed9eba1, 5) + c; a = RL(a, 10); + + // right + dd = RL(dd + f3(ee,aa,bb) + X[15] + 0x6d703ef3, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f3(dd,ee,aa) + X[ 5] + 0x6d703ef3, 7) + bb; ee = RL(ee, 10); + bb = RL(bb + f3(cc,dd,ee) + X[ 1] + 0x6d703ef3, 15) + aa; dd = RL(dd, 10); + aa = RL(aa + f3(bb,cc,dd) + X[ 3] + 0x6d703ef3, 11) + ee; cc = RL(cc, 10); + ee = RL(ee + f3(aa,bb,cc) + X[ 7] + 0x6d703ef3, 8) + dd; bb = RL(bb, 10); + dd = RL(dd + f3(ee,aa,bb) + X[14] + 0x6d703ef3, 6) + cc; aa = RL(aa, 10); + cc = RL(cc + f3(dd,ee,aa) + X[ 6] + 0x6d703ef3, 6) + bb; ee = RL(ee, 10); + bb = RL(bb + f3(cc,dd,ee) + X[ 9] + 0x6d703ef3, 14) + aa; dd = RL(dd, 10); + aa = RL(aa + f3(bb,cc,dd) + X[11] + 0x6d703ef3, 12) + ee; cc = RL(cc, 10); + ee = RL(ee + f3(aa,bb,cc) + X[ 8] + 0x6d703ef3, 13) + dd; bb = RL(bb, 10); + dd = RL(dd + f3(ee,aa,bb) + X[12] + 0x6d703ef3, 5) + cc; aa = RL(aa, 10); + cc = RL(cc + f3(dd,ee,aa) + X[ 2] + 0x6d703ef3, 14) + bb; ee = RL(ee, 10); + bb = RL(bb + f3(cc,dd,ee) + X[10] + 0x6d703ef3, 13) + aa; dd = RL(dd, 10); + aa = RL(aa + f3(bb,cc,dd) + X[ 0] + 0x6d703ef3, 13) + ee; cc = RL(cc, 10); + ee = RL(ee + f3(aa,bb,cc) + X[ 4] + 0x6d703ef3, 7) + dd; bb = RL(bb, 10); + dd = RL(dd + f3(ee,aa,bb) + X[13] + 0x6d703ef3, 5) + cc; aa = RL(aa, 10); + + t = c; c = cc; cc = t; + + // + // Rounds 48-63 + // + // left + c = RL(c + f4(d,e,a) + X[ 1] + 0x8f1bbcdc, 11) + b; e = RL(e, 10); + b = RL(b + f4(c,d,e) + X[ 9] + 0x8f1bbcdc, 12) + a; d = RL(d, 10); + a = RL(a + f4(b,c,d) + X[11] + 0x8f1bbcdc, 14) + e; c = RL(c, 10); + e = RL(e + f4(a,b,c) + X[10] + 0x8f1bbcdc, 15) + d; b = RL(b, 10); + d = RL(d + f4(e,a,b) + X[ 0] + 0x8f1bbcdc, 14) + c; a = RL(a, 10); + c = RL(c + f4(d,e,a) + X[ 8] + 0x8f1bbcdc, 15) + b; e = RL(e, 10); + b = RL(b + f4(c,d,e) + X[12] + 0x8f1bbcdc, 9) + a; d = RL(d, 10); + a = RL(a + f4(b,c,d) + X[ 4] + 0x8f1bbcdc, 8) + e; c = RL(c, 10); + e = RL(e + f4(a,b,c) + X[13] + 0x8f1bbcdc, 9) + d; b = RL(b, 10); + d = RL(d + f4(e,a,b) + X[ 3] + 0x8f1bbcdc, 14) + c; a = RL(a, 10); + c = RL(c + f4(d,e,a) + X[ 7] + 0x8f1bbcdc, 5) + b; e = RL(e, 10); + b = RL(b + f4(c,d,e) + X[15] + 0x8f1bbcdc, 6) + a; d = RL(d, 10); + a = RL(a + f4(b,c,d) + X[14] + 0x8f1bbcdc, 8) + e; c = RL(c, 10); + e = RL(e + f4(a,b,c) + X[ 5] + 0x8f1bbcdc, 6) + d; b = RL(b, 10); + d = RL(d + f4(e,a,b) + X[ 6] + 0x8f1bbcdc, 5) + c; a = RL(a, 10); + c = RL(c + f4(d,e,a) + X[ 2] + 0x8f1bbcdc, 12) + b; e = RL(e, 10); + + // right + cc = RL(cc + f2(dd,ee,aa) + X[ 8] + 0x7a6d76e9, 15) + bb; ee = RL(ee, 10); + bb = RL(bb + f2(cc,dd,ee) + X[ 6] + 0x7a6d76e9, 5) + aa; dd = RL(dd, 10); + aa = RL(aa + f2(bb,cc,dd) + X[ 4] + 0x7a6d76e9, 8) + ee; cc = RL(cc, 10); + ee = RL(ee + f2(aa,bb,cc) + X[ 1] + 0x7a6d76e9, 11) + dd; bb = RL(bb, 10); + dd = RL(dd + f2(ee,aa,bb) + X[ 3] + 0x7a6d76e9, 14) + cc; aa = RL(aa, 10); + cc = RL(cc + f2(dd,ee,aa) + X[11] + 0x7a6d76e9, 14) + bb; ee = RL(ee, 10); + bb = RL(bb + f2(cc,dd,ee) + X[15] + 0x7a6d76e9, 6) + aa; dd = RL(dd, 10); + aa = RL(aa + f2(bb,cc,dd) + X[ 0] + 0x7a6d76e9, 14) + ee; cc = RL(cc, 10); + ee = RL(ee + f2(aa,bb,cc) + X[ 5] + 0x7a6d76e9, 6) + dd; bb = RL(bb, 10); + dd = RL(dd + f2(ee,aa,bb) + X[12] + 0x7a6d76e9, 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f2(dd,ee,aa) + X[ 2] + 0x7a6d76e9, 12) + bb; ee = RL(ee, 10); + bb = RL(bb + f2(cc,dd,ee) + X[13] + 0x7a6d76e9, 9) + aa; dd = RL(dd, 10); + aa = RL(aa + f2(bb,cc,dd) + X[ 9] + 0x7a6d76e9, 12) + ee; cc = RL(cc, 10); + ee = RL(ee + f2(aa,bb,cc) + X[ 7] + 0x7a6d76e9, 5) + dd; bb = RL(bb, 10); + dd = RL(dd + f2(ee,aa,bb) + X[10] + 0x7a6d76e9, 15) + cc; aa = RL(aa, 10); + cc = RL(cc + f2(dd,ee,aa) + X[14] + 0x7a6d76e9, 8) + bb; ee = RL(ee, 10); + + t = d; d = dd; dd = t; + + // + // Rounds 64-79 + // + // left + b = RL(b + f5(c,d,e) + X[ 4] + 0xa953fd4e, 9) + a; d = RL(d, 10); + a = RL(a + f5(b,c,d) + X[ 0] + 0xa953fd4e, 15) + e; c = RL(c, 10); + e = RL(e + f5(a,b,c) + X[ 5] + 0xa953fd4e, 5) + d; b = RL(b, 10); + d = RL(d + f5(e,a,b) + X[ 9] + 0xa953fd4e, 11) + c; a = RL(a, 10); + c = RL(c + f5(d,e,a) + X[ 7] + 0xa953fd4e, 6) + b; e = RL(e, 10); + b = RL(b + f5(c,d,e) + X[12] + 0xa953fd4e, 8) + a; d = RL(d, 10); + a = RL(a + f5(b,c,d) + X[ 2] + 0xa953fd4e, 13) + e; c = RL(c, 10); + e = RL(e + f5(a,b,c) + X[10] + 0xa953fd4e, 12) + d; b = RL(b, 10); + d = RL(d + f5(e,a,b) + X[14] + 0xa953fd4e, 5) + c; a = RL(a, 10); + c = RL(c + f5(d,e,a) + X[ 1] + 0xa953fd4e, 12) + b; e = RL(e, 10); + b = RL(b + f5(c,d,e) + X[ 3] + 0xa953fd4e, 13) + a; d = RL(d, 10); + a = RL(a + f5(b,c,d) + X[ 8] + 0xa953fd4e, 14) + e; c = RL(c, 10); + e = RL(e + f5(a,b,c) + X[11] + 0xa953fd4e, 11) + d; b = RL(b, 10); + d = RL(d + f5(e,a,b) + X[ 6] + 0xa953fd4e, 8) + c; a = RL(a, 10); + c = RL(c + f5(d,e,a) + X[15] + 0xa953fd4e, 5) + b; e = RL(e, 10); + b = RL(b + f5(c,d,e) + X[13] + 0xa953fd4e, 6) + a; d = RL(d, 10); + + // right + bb = RL(bb + f1(cc,dd,ee) + X[12], 8) + aa; dd = RL(dd, 10); + aa = RL(aa + f1(bb,cc,dd) + X[15], 5) + ee; cc = RL(cc, 10); + ee = RL(ee + f1(aa,bb,cc) + X[10], 12) + dd; bb = RL(bb, 10); + dd = RL(dd + f1(ee,aa,bb) + X[ 4], 9) + cc; aa = RL(aa, 10); + cc = RL(cc + f1(dd,ee,aa) + X[ 1], 12) + bb; ee = RL(ee, 10); + bb = RL(bb + f1(cc,dd,ee) + X[ 5], 5) + aa; dd = RL(dd, 10); + aa = RL(aa + f1(bb,cc,dd) + X[ 8], 14) + ee; cc = RL(cc, 10); + ee = RL(ee + f1(aa,bb,cc) + X[ 7], 6) + dd; bb = RL(bb, 10); + dd = RL(dd + f1(ee,aa,bb) + X[ 6], 8) + cc; aa = RL(aa, 10); + cc = RL(cc + f1(dd,ee,aa) + X[ 2], 13) + bb; ee = RL(ee, 10); + bb = RL(bb + f1(cc,dd,ee) + X[13], 6) + aa; dd = RL(dd, 10); + aa = RL(aa + f1(bb,cc,dd) + X[14], 5) + ee; cc = RL(cc, 10); + ee = RL(ee + f1(aa,bb,cc) + X[ 0], 15) + dd; bb = RL(bb, 10); + dd = RL(dd + f1(ee,aa,bb) + X[ 3], 13) + cc; aa = RL(aa, 10); + cc = RL(cc + f1(dd,ee,aa) + X[ 9], 11) + bb; ee = RL(ee, 10); + bb = RL(bb + f1(cc,dd,ee) + X[11], 11) + aa; dd = RL(dd, 10); + + // + // do (e, ee) swap as part of assignment. + // + + H0 += a; + H1 += b; + H2 += c; + H3 += d; + H4 += ee; + H5 += aa; + H6 += bb; + H7 += cc; + H8 += dd; + H9 += e; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/SHA1Digest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/SHA1Digest.java new file mode 100644 index 000000000..883828833 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/SHA1Digest.java @@ -0,0 +1,290 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + +import com.google.bitcoin.bouncycastle.crypto.util.Pack; + +/** + * implementation of SHA-1 as outlined in "Handbook of Applied Cryptography", pages 346 - 349. + * + * It is interesting to ponder why the, apart from the extra IV, the other difference here from MD5 + * is the "endienness" of the word processing! + */ +public class SHA1Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 20; + + private int H1, H2, H3, H4, H5; + + private int[] X = new int[80]; + private int xOff; + + /** + * Standard constructor + */ + public SHA1Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public SHA1Digest(SHA1Digest t) + { + super(t); + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + H5 = t.H5; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "SHA-1"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + // Note: Inlined for performance +// X[xOff] = Pack.bigEndianToInt(in, inOff); + int n = in[ inOff] << 24; + n |= (in[++inOff] & 0xff) << 16; + n |= (in[++inOff] & 0xff) << 8; + n |= (in[++inOff] & 0xff); + X[xOff] = n; + + if (++xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength >>> 32); + X[15] = (int)(bitLength & 0xffffffff); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + Pack.intToBigEndian(H1, out, outOff); + Pack.intToBigEndian(H2, out, outOff + 4); + Pack.intToBigEndian(H3, out, outOff + 8); + Pack.intToBigEndian(H4, out, outOff + 12); + Pack.intToBigEndian(H5, out, outOff + 16); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables + */ + public void reset() + { + super.reset(); + + H1 = 0x67452301; + H2 = 0xefcdab89; + H3 = 0x98badcfe; + H4 = 0x10325476; + H5 = 0xc3d2e1f0; + + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + // + // Additive constants + // + private static final int Y1 = 0x5a827999; + private static final int Y2 = 0x6ed9eba1; + private static final int Y3 = 0x8f1bbcdc; + private static final int Y4 = 0xca62c1d6; + + private int f( + int u, + int v, + int w) + { + return ((u & v) | ((~u) & w)); + } + + private int h( + int u, + int v, + int w) + { + return (u ^ v ^ w); + } + + private int g( + int u, + int v, + int w) + { + return ((u & v) | (u & w) | (v & w)); + } + + protected void processBlock() + { + // + // expand 16 word block into 80 word block. + // + for (int i = 16; i < 80; i++) + { + int t = X[i - 3] ^ X[i - 8] ^ X[i - 14] ^ X[i - 16]; + X[i] = t << 1 | t >>> 31; + } + + // + // set up working variables. + // + int A = H1; + int B = H2; + int C = H3; + int D = H4; + int E = H5; + + // + // round 1 + // + int idx = 0; + + for (int j = 0; j < 4; j++) + { + // E = rotateLeft(A, 5) + f(B, C, D) + E + X[idx++] + Y1 + // B = rotateLeft(B, 30) + E += (A << 5 | A >>> 27) + f(B, C, D) + X[idx++] + Y1; + B = B << 30 | B >>> 2; + + D += (E << 5 | E >>> 27) + f(A, B, C) + X[idx++] + Y1; + A = A << 30 | A >>> 2; + + C += (D << 5 | D >>> 27) + f(E, A, B) + X[idx++] + Y1; + E = E << 30 | E >>> 2; + + B += (C << 5 | C >>> 27) + f(D, E, A) + X[idx++] + Y1; + D = D << 30 | D >>> 2; + + A += (B << 5 | B >>> 27) + f(C, D, E) + X[idx++] + Y1; + C = C << 30 | C >>> 2; + } + + // + // round 2 + // + for (int j = 0; j < 4; j++) + { + // E = rotateLeft(A, 5) + h(B, C, D) + E + X[idx++] + Y2 + // B = rotateLeft(B, 30) + E += (A << 5 | A >>> 27) + h(B, C, D) + X[idx++] + Y2; + B = B << 30 | B >>> 2; + + D += (E << 5 | E >>> 27) + h(A, B, C) + X[idx++] + Y2; + A = A << 30 | A >>> 2; + + C += (D << 5 | D >>> 27) + h(E, A, B) + X[idx++] + Y2; + E = E << 30 | E >>> 2; + + B += (C << 5 | C >>> 27) + h(D, E, A) + X[idx++] + Y2; + D = D << 30 | D >>> 2; + + A += (B << 5 | B >>> 27) + h(C, D, E) + X[idx++] + Y2; + C = C << 30 | C >>> 2; + } + + // + // round 3 + // + for (int j = 0; j < 4; j++) + { + // E = rotateLeft(A, 5) + g(B, C, D) + E + X[idx++] + Y3 + // B = rotateLeft(B, 30) + E += (A << 5 | A >>> 27) + g(B, C, D) + X[idx++] + Y3; + B = B << 30 | B >>> 2; + + D += (E << 5 | E >>> 27) + g(A, B, C) + X[idx++] + Y3; + A = A << 30 | A >>> 2; + + C += (D << 5 | D >>> 27) + g(E, A, B) + X[idx++] + Y3; + E = E << 30 | E >>> 2; + + B += (C << 5 | C >>> 27) + g(D, E, A) + X[idx++] + Y3; + D = D << 30 | D >>> 2; + + A += (B << 5 | B >>> 27) + g(C, D, E) + X[idx++] + Y3; + C = C << 30 | C >>> 2; + } + + // + // round 4 + // + for (int j = 0; j <= 3; j++) + { + // E = rotateLeft(A, 5) + h(B, C, D) + E + X[idx++] + Y4 + // B = rotateLeft(B, 30) + E += (A << 5 | A >>> 27) + h(B, C, D) + X[idx++] + Y4; + B = B << 30 | B >>> 2; + + D += (E << 5 | E >>> 27) + h(A, B, C) + X[idx++] + Y4; + A = A << 30 | A >>> 2; + + C += (D << 5 | D >>> 27) + h(E, A, B) + X[idx++] + Y4; + E = E << 30 | E >>> 2; + + B += (C << 5 | C >>> 27) + h(D, E, A) + X[idx++] + Y4; + D = D << 30 | D >>> 2; + + A += (B << 5 | B >>> 27) + h(C, D, E) + X[idx++] + Y4; + C = C << 30 | C >>> 2; + } + + + H1 += A; + H2 += B; + H3 += C; + H4 += D; + H5 += E; + + // + // reset start of the buffer. + // + xOff = 0; + for (int i = 0; i < 16; i++) + { + X[i] = 0; + } + } +} + + + + diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/SHA224Digest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/SHA224Digest.java new file mode 100644 index 000000000..b72509c23 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/SHA224Digest.java @@ -0,0 +1,292 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + + +import com.google.bitcoin.bouncycastle.crypto.digests.GeneralDigest; +import com.google.bitcoin.bouncycastle.crypto.util.Pack; + + +/** + * SHA-224 as described in RFC 3874 + *

+ *         block  word  digest
+ * SHA-1   512    32    160
+ * SHA-224 512    32    224
+ * SHA-256 512    32    256
+ * SHA-384 1024   64    384
+ * SHA-512 1024   64    512
+ * 
+ */ +public class SHA224Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 28; + + private int H1, H2, H3, H4, H5, H6, H7, H8; + + private int[] X = new int[64]; + private int xOff; + + /** + * Standard constructor + */ + public SHA224Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public SHA224Digest(SHA224Digest t) + { + super(t); + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + H5 = t.H5; + H6 = t.H6; + H7 = t.H7; + H8 = t.H8; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "SHA-224"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + // Note: Inlined for performance +// X[xOff] = Pack.bigEndianToInt(in, inOff); + int n = in[ inOff] << 24; + n |= (in[++inOff] & 0xff) << 16; + n |= (in[++inOff] & 0xff) << 8; + n |= (in[++inOff] & 0xff); + X[xOff] = n; + + if (++xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength >>> 32); + X[15] = (int)(bitLength & 0xffffffff); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + Pack.intToBigEndian(H1, out, outOff); + Pack.intToBigEndian(H2, out, outOff + 4); + Pack.intToBigEndian(H3, out, outOff + 8); + Pack.intToBigEndian(H4, out, outOff + 12); + Pack.intToBigEndian(H5, out, outOff + 16); + Pack.intToBigEndian(H6, out, outOff + 20); + Pack.intToBigEndian(H7, out, outOff + 24); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables + */ + public void reset() + { + super.reset(); + + /* SHA-224 initial hash value + */ + + H1 = 0xc1059ed8; + H2 = 0x367cd507; + H3 = 0x3070dd17; + H4 = 0xf70e5939; + H5 = 0xffc00b31; + H6 = 0x68581511; + H7 = 0x64f98fa7; + H8 = 0xbefa4fa4; + + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + protected void processBlock() + { + // + // expand 16 word block into 64 word blocks. + // + for (int t = 16; t <= 63; t++) + { + X[t] = Theta1(X[t - 2]) + X[t - 7] + Theta0(X[t - 15]) + X[t - 16]; + } + + // + // set up working variables. + // + int a = H1; + int b = H2; + int c = H3; + int d = H4; + int e = H5; + int f = H6; + int g = H7; + int h = H8; + + + int t = 0; + for(int i = 0; i < 8; i ++) + { + // t = 8 * i + h += Sum1(e) + Ch(e, f, g) + K[t] + X[t]; + d += h; + h += Sum0(a) + Maj(a, b, c); + ++t; + + // t = 8 * i + 1 + g += Sum1(d) + Ch(d, e, f) + K[t] + X[t]; + c += g; + g += Sum0(h) + Maj(h, a, b); + ++t; + + // t = 8 * i + 2 + f += Sum1(c) + Ch(c, d, e) + K[t] + X[t]; + b += f; + f += Sum0(g) + Maj(g, h, a); + ++t; + + // t = 8 * i + 3 + e += Sum1(b) + Ch(b, c, d) + K[t] + X[t]; + a += e; + e += Sum0(f) + Maj(f, g, h); + ++t; + + // t = 8 * i + 4 + d += Sum1(a) + Ch(a, b, c) + K[t] + X[t]; + h += d; + d += Sum0(e) + Maj(e, f, g); + ++t; + + // t = 8 * i + 5 + c += Sum1(h) + Ch(h, a, b) + K[t] + X[t]; + g += c; + c += Sum0(d) + Maj(d, e, f); + ++t; + + // t = 8 * i + 6 + b += Sum1(g) + Ch(g, h, a) + K[t] + X[t]; + f += b; + b += Sum0(c) + Maj(c, d, e); + ++t; + + // t = 8 * i + 7 + a += Sum1(f) + Ch(f, g, h) + K[t] + X[t]; + e += a; + a += Sum0(b) + Maj(b, c, d); + ++t; + } + + H1 += a; + H2 += b; + H3 += c; + H4 += d; + H5 += e; + H6 += f; + H7 += g; + H8 += h; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i < 16; i++) + { + X[i] = 0; + } + } + + /* SHA-224 functions */ + private int Ch( + int x, + int y, + int z) + { + return ((x & y) ^ ((~x) & z)); + } + + private int Maj( + int x, + int y, + int z) + { + return ((x & y) ^ (x & z) ^ (y & z)); + } + + private int Sum0( + int x) + { + return ((x >>> 2) | (x << 30)) ^ ((x >>> 13) | (x << 19)) ^ ((x >>> 22) | (x << 10)); + } + + private int Sum1( + int x) + { + return ((x >>> 6) | (x << 26)) ^ ((x >>> 11) | (x << 21)) ^ ((x >>> 25) | (x << 7)); + } + + private int Theta0( + int x) + { + return ((x >>> 7) | (x << 25)) ^ ((x >>> 18) | (x << 14)) ^ (x >>> 3); + } + + private int Theta1( + int x) + { + return ((x >>> 17) | (x << 15)) ^ ((x >>> 19) | (x << 13)) ^ (x >>> 10); + } + + /* SHA-224 Constants + * (represent the first 32 bits of the fractional parts of the + * cube roots of the first sixty-four prime numbers) + */ + static final int K[] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + }; +} + diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/SHA256Digest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/SHA256Digest.java new file mode 100644 index 000000000..ddfd9bb57 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/SHA256Digest.java @@ -0,0 +1,295 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + + +import com.google.bitcoin.bouncycastle.crypto.digests.GeneralDigest; +import com.google.bitcoin.bouncycastle.crypto.util.Pack; + + +/** + * FIPS 180-2 implementation of SHA-256. + * + *
+ *         block  word  digest
+ * SHA-1   512    32    160
+ * SHA-256 512    32    256
+ * SHA-384 1024   64    384
+ * SHA-512 1024   64    512
+ * 
+ */ +public class SHA256Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 32; + + private int H1, H2, H3, H4, H5, H6, H7, H8; + + private int[] X = new int[64]; + private int xOff; + + /** + * Standard constructor + */ + public SHA256Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public SHA256Digest(SHA256Digest t) + { + super(t); + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + H5 = t.H5; + H6 = t.H6; + H7 = t.H7; + H8 = t.H8; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "SHA-256"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + // Note: Inlined for performance +// X[xOff] = Pack.bigEndianToInt(in, inOff); + int n = in[inOff] << 24; + n |= (in[++inOff] & 0xff) << 16; + n |= (in[++inOff] & 0xff) << 8; + n |= (in[++inOff] & 0xff); + X[xOff] = n; + + if (++xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength >>> 32); + X[15] = (int)(bitLength & 0xffffffff); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + Pack.intToBigEndian(H1, out, outOff); + Pack.intToBigEndian(H2, out, outOff + 4); + Pack.intToBigEndian(H3, out, outOff + 8); + Pack.intToBigEndian(H4, out, outOff + 12); + Pack.intToBigEndian(H5, out, outOff + 16); + Pack.intToBigEndian(H6, out, outOff + 20); + Pack.intToBigEndian(H7, out, outOff + 24); + Pack.intToBigEndian(H8, out, outOff + 28); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables + */ + public void reset() + { + super.reset(); + + /* SHA-256 initial hash value + * The first 32 bits of the fractional parts of the square roots + * of the first eight prime numbers + */ + + H1 = 0x6a09e667; + H2 = 0xbb67ae85; + H3 = 0x3c6ef372; + H4 = 0xa54ff53a; + H5 = 0x510e527f; + H6 = 0x9b05688c; + H7 = 0x1f83d9ab; + H8 = 0x5be0cd19; + + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + protected void processBlock() + { + // + // expand 16 word block into 64 word blocks. + // + for (int t = 16; t <= 63; t++) + { + X[t] = Theta1(X[t - 2]) + X[t - 7] + Theta0(X[t - 15]) + X[t - 16]; + } + + // + // set up working variables. + // + int a = H1; + int b = H2; + int c = H3; + int d = H4; + int e = H5; + int f = H6; + int g = H7; + int h = H8; + + int t = 0; + for(int i = 0; i < 8; i ++) + { + // t = 8 * i + h += Sum1(e) + Ch(e, f, g) + K[t] + X[t]; + d += h; + h += Sum0(a) + Maj(a, b, c); + ++t; + + // t = 8 * i + 1 + g += Sum1(d) + Ch(d, e, f) + K[t] + X[t]; + c += g; + g += Sum0(h) + Maj(h, a, b); + ++t; + + // t = 8 * i + 2 + f += Sum1(c) + Ch(c, d, e) + K[t] + X[t]; + b += f; + f += Sum0(g) + Maj(g, h, a); + ++t; + + // t = 8 * i + 3 + e += Sum1(b) + Ch(b, c, d) + K[t] + X[t]; + a += e; + e += Sum0(f) + Maj(f, g, h); + ++t; + + // t = 8 * i + 4 + d += Sum1(a) + Ch(a, b, c) + K[t] + X[t]; + h += d; + d += Sum0(e) + Maj(e, f, g); + ++t; + + // t = 8 * i + 5 + c += Sum1(h) + Ch(h, a, b) + K[t] + X[t]; + g += c; + c += Sum0(d) + Maj(d, e, f); + ++t; + + // t = 8 * i + 6 + b += Sum1(g) + Ch(g, h, a) + K[t] + X[t]; + f += b; + b += Sum0(c) + Maj(c, d, e); + ++t; + + // t = 8 * i + 7 + a += Sum1(f) + Ch(f, g, h) + K[t] + X[t]; + e += a; + a += Sum0(b) + Maj(b, c, d); + ++t; + } + + H1 += a; + H2 += b; + H3 += c; + H4 += d; + H5 += e; + H6 += f; + H7 += g; + H8 += h; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i < 16; i++) + { + X[i] = 0; + } + } + + /* SHA-256 functions */ + private int Ch( + int x, + int y, + int z) + { + return (x & y) ^ ((~x) & z); + } + + private int Maj( + int x, + int y, + int z) + { + return (x & y) ^ (x & z) ^ (y & z); + } + + private int Sum0( + int x) + { + return ((x >>> 2) | (x << 30)) ^ ((x >>> 13) | (x << 19)) ^ ((x >>> 22) | (x << 10)); + } + + private int Sum1( + int x) + { + return ((x >>> 6) | (x << 26)) ^ ((x >>> 11) | (x << 21)) ^ ((x >>> 25) | (x << 7)); + } + + private int Theta0( + int x) + { + return ((x >>> 7) | (x << 25)) ^ ((x >>> 18) | (x << 14)) ^ (x >>> 3); + } + + private int Theta1( + int x) + { + return ((x >>> 17) | (x << 15)) ^ ((x >>> 19) | (x << 13)) ^ (x >>> 10); + } + + /* SHA-256 Constants + * (represent the first 32 bits of the fractional parts of the + * cube roots of the first sixty-four prime numbers) + */ + static final int K[] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + }; +} + diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/SHA384Digest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/SHA384Digest.java new file mode 100644 index 000000000..536061f5a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/SHA384Digest.java @@ -0,0 +1,87 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + +import com.google.bitcoin.bouncycastle.crypto.util.Pack; + + +/** + * FIPS 180-2 implementation of SHA-384. + * + *
+ *         block  word  digest
+ * SHA-1   512    32    160
+ * SHA-256 512    32    256
+ * SHA-384 1024   64    384
+ * SHA-512 1024   64    512
+ * 
+ */ +public class SHA384Digest + extends LongDigest +{ + + private static final int DIGEST_LENGTH = 48; + + /** + * Standard constructor + */ + public SHA384Digest() + { + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public SHA384Digest(SHA384Digest t) + { + super(t); + } + + public String getAlgorithmName() + { + return "SHA-384"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + Pack.longToBigEndian(H1, out, outOff); + Pack.longToBigEndian(H2, out, outOff + 8); + Pack.longToBigEndian(H3, out, outOff + 16); + Pack.longToBigEndian(H4, out, outOff + 24); + Pack.longToBigEndian(H5, out, outOff + 32); + Pack.longToBigEndian(H6, out, outOff + 40); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables + */ + public void reset() + { + super.reset(); + + /* SHA-384 initial hash value + * The first 64 bits of the fractional parts of the square roots + * of the 9th through 16th prime numbers + */ + H1 = 0xcbbb9d5dc1059ed8l; + H2 = 0x629a292a367cd507l; + H3 = 0x9159015a3070dd17l; + H4 = 0x152fecd8f70e5939l; + H5 = 0x67332667ffc00b31l; + H6 = 0x8eb44a8768581511l; + H7 = 0xdb0c2e0d64f98fa7l; + H8 = 0x47b5481dbefa4fa4l; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/SHA512Digest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/SHA512Digest.java new file mode 100644 index 000000000..5841f2a63 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/SHA512Digest.java @@ -0,0 +1,89 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + +import com.google.bitcoin.bouncycastle.crypto.util.Pack; + + +/** + * FIPS 180-2 implementation of SHA-512. + * + *
+ *         block  word  digest
+ * SHA-1   512    32    160
+ * SHA-256 512    32    256
+ * SHA-384 1024   64    384
+ * SHA-512 1024   64    512
+ * 
+ */ +public class SHA512Digest + extends LongDigest +{ + private static final int DIGEST_LENGTH = 64; + + /** + * Standard constructor + */ + public SHA512Digest() + { + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public SHA512Digest(SHA512Digest t) + { + super(t); + } + + public String getAlgorithmName() + { + return "SHA-512"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + Pack.longToBigEndian(H1, out, outOff); + Pack.longToBigEndian(H2, out, outOff + 8); + Pack.longToBigEndian(H3, out, outOff + 16); + Pack.longToBigEndian(H4, out, outOff + 24); + Pack.longToBigEndian(H5, out, outOff + 32); + Pack.longToBigEndian(H6, out, outOff + 40); + Pack.longToBigEndian(H7, out, outOff + 48); + Pack.longToBigEndian(H8, out, outOff + 56); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables + */ + public void reset() + { + super.reset(); + + /* SHA-512 initial hash value + * The first 64 bits of the fractional parts of the square roots + * of the first eight prime numbers + */ + H1 = 0x6a09e667f3bcc908L; + H2 = 0xbb67ae8584caa73bL; + H3 = 0x3c6ef372fe94f82bL; + H4 = 0xa54ff53a5f1d36f1L; + H5 = 0x510e527fade682d1L; + H6 = 0x9b05688c2b3e6c1fL; + H7 = 0x1f83d9abfb41bd6bL; + H8 = 0x5be0cd19137e2179L; + } +} + diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/ShortenedDigest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/ShortenedDigest.java new file mode 100644 index 000000000..471e59c17 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/ShortenedDigest.java @@ -0,0 +1,80 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + +import com.google.bitcoin.bouncycastle.crypto.ExtendedDigest; + +/** + * Wrapper class that reduces the output length of a particular digest to + * only the first n bytes of the digest function. + */ +public class ShortenedDigest + implements ExtendedDigest +{ + private ExtendedDigest baseDigest; + private int length; + + /** + * Base constructor. + * + * @param baseDigest underlying digest to use. + * @param length length in bytes of the output of doFinal. + * @exception IllegalArgumentException if baseDigest is null, or length is greater than baseDigest.getDigestSize(). + */ + public ShortenedDigest( + ExtendedDigest baseDigest, + int length) + { + if (baseDigest == null) + { + throw new IllegalArgumentException("baseDigest must not be null"); + } + + if (length > baseDigest.getDigestSize()) + { + throw new IllegalArgumentException("baseDigest output not large enough to support length"); + } + + this.baseDigest = baseDigest; + this.length = length; + } + + public String getAlgorithmName() + { + return baseDigest.getAlgorithmName() + "(" + length * 8 + ")"; + } + + public int getDigestSize() + { + return length; + } + + public void update(byte in) + { + baseDigest.update(in); + } + + public void update(byte[] in, int inOff, int len) + { + baseDigest.update(in, inOff, len); + } + + public int doFinal(byte[] out, int outOff) + { + byte[] tmp = new byte[baseDigest.getDigestSize()]; + + baseDigest.doFinal(tmp, 0); + + System.arraycopy(tmp, 0, out, outOff, length); + + return length; + } + + public void reset() + { + baseDigest.reset(); + } + + public int getByteLength() + { + return baseDigest.getByteLength(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/TigerDigest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/TigerDigest.java new file mode 100644 index 000000000..7b66ea974 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/TigerDigest.java @@ -0,0 +1,866 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + +import com.google.bitcoin.bouncycastle.crypto.ExtendedDigest; + +/** + * implementation of Tiger based on: + * + * http://www.cs.technion.ac.il/~biham/Reports/Tiger + */ +public class TigerDigest + implements ExtendedDigest +{ + private static final int BYTE_LENGTH = 64; + + /* + * S-Boxes. + */ + private static final long[] t1 = { + 0x02AAB17CF7E90C5EL /* 0 */, 0xAC424B03E243A8ECL /* 1 */, + 0x72CD5BE30DD5FCD3L /* 2 */, 0x6D019B93F6F97F3AL /* 3 */, + 0xCD9978FFD21F9193L /* 4 */, 0x7573A1C9708029E2L /* 5 */, + 0xB164326B922A83C3L /* 6 */, 0x46883EEE04915870L /* 7 */, + 0xEAACE3057103ECE6L /* 8 */, 0xC54169B808A3535CL /* 9 */, + 0x4CE754918DDEC47CL /* 10 */, 0x0AA2F4DFDC0DF40CL /* 11 */, + 0x10B76F18A74DBEFAL /* 12 */, 0xC6CCB6235AD1AB6AL /* 13 */, + 0x13726121572FE2FFL /* 14 */, 0x1A488C6F199D921EL /* 15 */, + 0x4BC9F9F4DA0007CAL /* 16 */, 0x26F5E6F6E85241C7L /* 17 */, + 0x859079DBEA5947B6L /* 18 */, 0x4F1885C5C99E8C92L /* 19 */, + 0xD78E761EA96F864BL /* 20 */, 0x8E36428C52B5C17DL /* 21 */, + 0x69CF6827373063C1L /* 22 */, 0xB607C93D9BB4C56EL /* 23 */, + 0x7D820E760E76B5EAL /* 24 */, 0x645C9CC6F07FDC42L /* 25 */, + 0xBF38A078243342E0L /* 26 */, 0x5F6B343C9D2E7D04L /* 27 */, + 0xF2C28AEB600B0EC6L /* 28 */, 0x6C0ED85F7254BCACL /* 29 */, + 0x71592281A4DB4FE5L /* 30 */, 0x1967FA69CE0FED9FL /* 31 */, + 0xFD5293F8B96545DBL /* 32 */, 0xC879E9D7F2A7600BL /* 33 */, + 0x860248920193194EL /* 34 */, 0xA4F9533B2D9CC0B3L /* 35 */, + 0x9053836C15957613L /* 36 */, 0xDB6DCF8AFC357BF1L /* 37 */, + 0x18BEEA7A7A370F57L /* 38 */, 0x037117CA50B99066L /* 39 */, + 0x6AB30A9774424A35L /* 40 */, 0xF4E92F02E325249BL /* 41 */, + 0x7739DB07061CCAE1L /* 42 */, 0xD8F3B49CECA42A05L /* 43 */, + 0xBD56BE3F51382F73L /* 44 */, 0x45FAED5843B0BB28L /* 45 */, + 0x1C813D5C11BF1F83L /* 46 */, 0x8AF0E4B6D75FA169L /* 47 */, + 0x33EE18A487AD9999L /* 48 */, 0x3C26E8EAB1C94410L /* 49 */, + 0xB510102BC0A822F9L /* 50 */, 0x141EEF310CE6123BL /* 51 */, + 0xFC65B90059DDB154L /* 52 */, 0xE0158640C5E0E607L /* 53 */, + 0x884E079826C3A3CFL /* 54 */, 0x930D0D9523C535FDL /* 55 */, + 0x35638D754E9A2B00L /* 56 */, 0x4085FCCF40469DD5L /* 57 */, + 0xC4B17AD28BE23A4CL /* 58 */, 0xCAB2F0FC6A3E6A2EL /* 59 */, + 0x2860971A6B943FCDL /* 60 */, 0x3DDE6EE212E30446L /* 61 */, + 0x6222F32AE01765AEL /* 62 */, 0x5D550BB5478308FEL /* 63 */, + 0xA9EFA98DA0EDA22AL /* 64 */, 0xC351A71686C40DA7L /* 65 */, + 0x1105586D9C867C84L /* 66 */, 0xDCFFEE85FDA22853L /* 67 */, + 0xCCFBD0262C5EEF76L /* 68 */, 0xBAF294CB8990D201L /* 69 */, + 0xE69464F52AFAD975L /* 70 */, 0x94B013AFDF133E14L /* 71 */, + 0x06A7D1A32823C958L /* 72 */, 0x6F95FE5130F61119L /* 73 */, + 0xD92AB34E462C06C0L /* 74 */, 0xED7BDE33887C71D2L /* 75 */, + 0x79746D6E6518393EL /* 76 */, 0x5BA419385D713329L /* 77 */, + 0x7C1BA6B948A97564L /* 78 */, 0x31987C197BFDAC67L /* 79 */, + 0xDE6C23C44B053D02L /* 80 */, 0x581C49FED002D64DL /* 81 */, + 0xDD474D6338261571L /* 82 */, 0xAA4546C3E473D062L /* 83 */, + 0x928FCE349455F860L /* 84 */, 0x48161BBACAAB94D9L /* 85 */, + 0x63912430770E6F68L /* 86 */, 0x6EC8A5E602C6641CL /* 87 */, + 0x87282515337DDD2BL /* 88 */, 0x2CDA6B42034B701BL /* 89 */, + 0xB03D37C181CB096DL /* 90 */, 0xE108438266C71C6FL /* 91 */, + 0x2B3180C7EB51B255L /* 92 */, 0xDF92B82F96C08BBCL /* 93 */, + 0x5C68C8C0A632F3BAL /* 94 */, 0x5504CC861C3D0556L /* 95 */, + 0xABBFA4E55FB26B8FL /* 96 */, 0x41848B0AB3BACEB4L /* 97 */, + 0xB334A273AA445D32L /* 98 */, 0xBCA696F0A85AD881L /* 99 */, + 0x24F6EC65B528D56CL /* 100 */, 0x0CE1512E90F4524AL /* 101 */, + 0x4E9DD79D5506D35AL /* 102 */, 0x258905FAC6CE9779L /* 103 */, + 0x2019295B3E109B33L /* 104 */, 0xF8A9478B73A054CCL /* 105 */, + 0x2924F2F934417EB0L /* 106 */, 0x3993357D536D1BC4L /* 107 */, + 0x38A81AC21DB6FF8BL /* 108 */, 0x47C4FBF17D6016BFL /* 109 */, + 0x1E0FAADD7667E3F5L /* 110 */, 0x7ABCFF62938BEB96L /* 111 */, + 0xA78DAD948FC179C9L /* 112 */, 0x8F1F98B72911E50DL /* 113 */, + 0x61E48EAE27121A91L /* 114 */, 0x4D62F7AD31859808L /* 115 */, + 0xECEBA345EF5CEAEBL /* 116 */, 0xF5CEB25EBC9684CEL /* 117 */, + 0xF633E20CB7F76221L /* 118 */, 0xA32CDF06AB8293E4L /* 119 */, + 0x985A202CA5EE2CA4L /* 120 */, 0xCF0B8447CC8A8FB1L /* 121 */, + 0x9F765244979859A3L /* 122 */, 0xA8D516B1A1240017L /* 123 */, + 0x0BD7BA3EBB5DC726L /* 124 */, 0xE54BCA55B86ADB39L /* 125 */, + 0x1D7A3AFD6C478063L /* 126 */, 0x519EC608E7669EDDL /* 127 */, + 0x0E5715A2D149AA23L /* 128 */, 0x177D4571848FF194L /* 129 */, + 0xEEB55F3241014C22L /* 130 */, 0x0F5E5CA13A6E2EC2L /* 131 */, + 0x8029927B75F5C361L /* 132 */, 0xAD139FABC3D6E436L /* 133 */, + 0x0D5DF1A94CCF402FL /* 134 */, 0x3E8BD948BEA5DFC8L /* 135 */, + 0xA5A0D357BD3FF77EL /* 136 */, 0xA2D12E251F74F645L /* 137 */, + 0x66FD9E525E81A082L /* 138 */, 0x2E0C90CE7F687A49L /* 139 */, + 0xC2E8BCBEBA973BC5L /* 140 */, 0x000001BCE509745FL /* 141 */, + 0x423777BBE6DAB3D6L /* 142 */, 0xD1661C7EAEF06EB5L /* 143 */, + 0xA1781F354DAACFD8L /* 144 */, 0x2D11284A2B16AFFCL /* 145 */, + 0xF1FC4F67FA891D1FL /* 146 */, 0x73ECC25DCB920ADAL /* 147 */, + 0xAE610C22C2A12651L /* 148 */, 0x96E0A810D356B78AL /* 149 */, + 0x5A9A381F2FE7870FL /* 150 */, 0xD5AD62EDE94E5530L /* 151 */, + 0xD225E5E8368D1427L /* 152 */, 0x65977B70C7AF4631L /* 153 */, + 0x99F889B2DE39D74FL /* 154 */, 0x233F30BF54E1D143L /* 155 */, + 0x9A9675D3D9A63C97L /* 156 */, 0x5470554FF334F9A8L /* 157 */, + 0x166ACB744A4F5688L /* 158 */, 0x70C74CAAB2E4AEADL /* 159 */, + 0xF0D091646F294D12L /* 160 */, 0x57B82A89684031D1L /* 161 */, + 0xEFD95A5A61BE0B6BL /* 162 */, 0x2FBD12E969F2F29AL /* 163 */, + 0x9BD37013FEFF9FE8L /* 164 */, 0x3F9B0404D6085A06L /* 165 */, + 0x4940C1F3166CFE15L /* 166 */, 0x09542C4DCDF3DEFBL /* 167 */, + 0xB4C5218385CD5CE3L /* 168 */, 0xC935B7DC4462A641L /* 169 */, + 0x3417F8A68ED3B63FL /* 170 */, 0xB80959295B215B40L /* 171 */, + 0xF99CDAEF3B8C8572L /* 172 */, 0x018C0614F8FCB95DL /* 173 */, + 0x1B14ACCD1A3ACDF3L /* 174 */, 0x84D471F200BB732DL /* 175 */, + 0xC1A3110E95E8DA16L /* 176 */, 0x430A7220BF1A82B8L /* 177 */, + 0xB77E090D39DF210EL /* 178 */, 0x5EF4BD9F3CD05E9DL /* 179 */, + 0x9D4FF6DA7E57A444L /* 180 */, 0xDA1D60E183D4A5F8L /* 181 */, + 0xB287C38417998E47L /* 182 */, 0xFE3EDC121BB31886L /* 183 */, + 0xC7FE3CCC980CCBEFL /* 184 */, 0xE46FB590189BFD03L /* 185 */, + 0x3732FD469A4C57DCL /* 186 */, 0x7EF700A07CF1AD65L /* 187 */, + 0x59C64468A31D8859L /* 188 */, 0x762FB0B4D45B61F6L /* 189 */, + 0x155BAED099047718L /* 190 */, 0x68755E4C3D50BAA6L /* 191 */, + 0xE9214E7F22D8B4DFL /* 192 */, 0x2ADDBF532EAC95F4L /* 193 */, + 0x32AE3909B4BD0109L /* 194 */, 0x834DF537B08E3450L /* 195 */, + 0xFA209DA84220728DL /* 196 */, 0x9E691D9B9EFE23F7L /* 197 */, + 0x0446D288C4AE8D7FL /* 198 */, 0x7B4CC524E169785BL /* 199 */, + 0x21D87F0135CA1385L /* 200 */, 0xCEBB400F137B8AA5L /* 201 */, + 0x272E2B66580796BEL /* 202 */, 0x3612264125C2B0DEL /* 203 */, + 0x057702BDAD1EFBB2L /* 204 */, 0xD4BABB8EACF84BE9L /* 205 */, + 0x91583139641BC67BL /* 206 */, 0x8BDC2DE08036E024L /* 207 */, + 0x603C8156F49F68EDL /* 208 */, 0xF7D236F7DBEF5111L /* 209 */, + 0x9727C4598AD21E80L /* 210 */, 0xA08A0896670A5FD7L /* 211 */, + 0xCB4A8F4309EBA9CBL /* 212 */, 0x81AF564B0F7036A1L /* 213 */, + 0xC0B99AA778199ABDL /* 214 */, 0x959F1EC83FC8E952L /* 215 */, + 0x8C505077794A81B9L /* 216 */, 0x3ACAAF8F056338F0L /* 217 */, + 0x07B43F50627A6778L /* 218 */, 0x4A44AB49F5ECCC77L /* 219 */, + 0x3BC3D6E4B679EE98L /* 220 */, 0x9CC0D4D1CF14108CL /* 221 */, + 0x4406C00B206BC8A0L /* 222 */, 0x82A18854C8D72D89L /* 223 */, + 0x67E366B35C3C432CL /* 224 */, 0xB923DD61102B37F2L /* 225 */, + 0x56AB2779D884271DL /* 226 */, 0xBE83E1B0FF1525AFL /* 227 */, + 0xFB7C65D4217E49A9L /* 228 */, 0x6BDBE0E76D48E7D4L /* 229 */, + 0x08DF828745D9179EL /* 230 */, 0x22EA6A9ADD53BD34L /* 231 */, + 0xE36E141C5622200AL /* 232 */, 0x7F805D1B8CB750EEL /* 233 */, + 0xAFE5C7A59F58E837L /* 234 */, 0xE27F996A4FB1C23CL /* 235 */, + 0xD3867DFB0775F0D0L /* 236 */, 0xD0E673DE6E88891AL /* 237 */, + 0x123AEB9EAFB86C25L /* 238 */, 0x30F1D5D5C145B895L /* 239 */, + 0xBB434A2DEE7269E7L /* 240 */, 0x78CB67ECF931FA38L /* 241 */, + 0xF33B0372323BBF9CL /* 242 */, 0x52D66336FB279C74L /* 243 */, + 0x505F33AC0AFB4EAAL /* 244 */, 0xE8A5CD99A2CCE187L /* 245 */, + 0x534974801E2D30BBL /* 246 */, 0x8D2D5711D5876D90L /* 247 */, + 0x1F1A412891BC038EL /* 248 */, 0xD6E2E71D82E56648L /* 249 */, + 0x74036C3A497732B7L /* 250 */, 0x89B67ED96361F5ABL /* 251 */, + 0xFFED95D8F1EA02A2L /* 252 */, 0xE72B3BD61464D43DL /* 253 */, + 0xA6300F170BDC4820L /* 254 */, 0xEBC18760ED78A77AL /* 255 */, + }; + + private static final long[] t2 = { + 0xE6A6BE5A05A12138L /* 256 */, 0xB5A122A5B4F87C98L /* 257 */, + 0x563C6089140B6990L /* 258 */, 0x4C46CB2E391F5DD5L /* 259 */, + 0xD932ADDBC9B79434L /* 260 */, 0x08EA70E42015AFF5L /* 261 */, + 0xD765A6673E478CF1L /* 262 */, 0xC4FB757EAB278D99L /* 263 */, + 0xDF11C6862D6E0692L /* 264 */, 0xDDEB84F10D7F3B16L /* 265 */, + 0x6F2EF604A665EA04L /* 266 */, 0x4A8E0F0FF0E0DFB3L /* 267 */, + 0xA5EDEEF83DBCBA51L /* 268 */, 0xFC4F0A2A0EA4371EL /* 269 */, + 0xE83E1DA85CB38429L /* 270 */, 0xDC8FF882BA1B1CE2L /* 271 */, + 0xCD45505E8353E80DL /* 272 */, 0x18D19A00D4DB0717L /* 273 */, + 0x34A0CFEDA5F38101L /* 274 */, 0x0BE77E518887CAF2L /* 275 */, + 0x1E341438B3C45136L /* 276 */, 0xE05797F49089CCF9L /* 277 */, + 0xFFD23F9DF2591D14L /* 278 */, 0x543DDA228595C5CDL /* 279 */, + 0x661F81FD99052A33L /* 280 */, 0x8736E641DB0F7B76L /* 281 */, + 0x15227725418E5307L /* 282 */, 0xE25F7F46162EB2FAL /* 283 */, + 0x48A8B2126C13D9FEL /* 284 */, 0xAFDC541792E76EEAL /* 285 */, + 0x03D912BFC6D1898FL /* 286 */, 0x31B1AAFA1B83F51BL /* 287 */, + 0xF1AC2796E42AB7D9L /* 288 */, 0x40A3A7D7FCD2EBACL /* 289 */, + 0x1056136D0AFBBCC5L /* 290 */, 0x7889E1DD9A6D0C85L /* 291 */, + 0xD33525782A7974AAL /* 292 */, 0xA7E25D09078AC09BL /* 293 */, + 0xBD4138B3EAC6EDD0L /* 294 */, 0x920ABFBE71EB9E70L /* 295 */, + 0xA2A5D0F54FC2625CL /* 296 */, 0xC054E36B0B1290A3L /* 297 */, + 0xF6DD59FF62FE932BL /* 298 */, 0x3537354511A8AC7DL /* 299 */, + 0xCA845E9172FADCD4L /* 300 */, 0x84F82B60329D20DCL /* 301 */, + 0x79C62CE1CD672F18L /* 302 */, 0x8B09A2ADD124642CL /* 303 */, + 0xD0C1E96A19D9E726L /* 304 */, 0x5A786A9B4BA9500CL /* 305 */, + 0x0E020336634C43F3L /* 306 */, 0xC17B474AEB66D822L /* 307 */, + 0x6A731AE3EC9BAAC2L /* 308 */, 0x8226667AE0840258L /* 309 */, + 0x67D4567691CAECA5L /* 310 */, 0x1D94155C4875ADB5L /* 311 */, + 0x6D00FD985B813FDFL /* 312 */, 0x51286EFCB774CD06L /* 313 */, + 0x5E8834471FA744AFL /* 314 */, 0xF72CA0AEE761AE2EL /* 315 */, + 0xBE40E4CDAEE8E09AL /* 316 */, 0xE9970BBB5118F665L /* 317 */, + 0x726E4BEB33DF1964L /* 318 */, 0x703B000729199762L /* 319 */, + 0x4631D816F5EF30A7L /* 320 */, 0xB880B5B51504A6BEL /* 321 */, + 0x641793C37ED84B6CL /* 322 */, 0x7B21ED77F6E97D96L /* 323 */, + 0x776306312EF96B73L /* 324 */, 0xAE528948E86FF3F4L /* 325 */, + 0x53DBD7F286A3F8F8L /* 326 */, 0x16CADCE74CFC1063L /* 327 */, + 0x005C19BDFA52C6DDL /* 328 */, 0x68868F5D64D46AD3L /* 329 */, + 0x3A9D512CCF1E186AL /* 330 */, 0x367E62C2385660AEL /* 331 */, + 0xE359E7EA77DCB1D7L /* 332 */, 0x526C0773749ABE6EL /* 333 */, + 0x735AE5F9D09F734BL /* 334 */, 0x493FC7CC8A558BA8L /* 335 */, + 0xB0B9C1533041AB45L /* 336 */, 0x321958BA470A59BDL /* 337 */, + 0x852DB00B5F46C393L /* 338 */, 0x91209B2BD336B0E5L /* 339 */, + 0x6E604F7D659EF19FL /* 340 */, 0xB99A8AE2782CCB24L /* 341 */, + 0xCCF52AB6C814C4C7L /* 342 */, 0x4727D9AFBE11727BL /* 343 */, + 0x7E950D0C0121B34DL /* 344 */, 0x756F435670AD471FL /* 345 */, + 0xF5ADD442615A6849L /* 346 */, 0x4E87E09980B9957AL /* 347 */, + 0x2ACFA1DF50AEE355L /* 348 */, 0xD898263AFD2FD556L /* 349 */, + 0xC8F4924DD80C8FD6L /* 350 */, 0xCF99CA3D754A173AL /* 351 */, + 0xFE477BACAF91BF3CL /* 352 */, 0xED5371F6D690C12DL /* 353 */, + 0x831A5C285E687094L /* 354 */, 0xC5D3C90A3708A0A4L /* 355 */, + 0x0F7F903717D06580L /* 356 */, 0x19F9BB13B8FDF27FL /* 357 */, + 0xB1BD6F1B4D502843L /* 358 */, 0x1C761BA38FFF4012L /* 359 */, + 0x0D1530C4E2E21F3BL /* 360 */, 0x8943CE69A7372C8AL /* 361 */, + 0xE5184E11FEB5CE66L /* 362 */, 0x618BDB80BD736621L /* 363 */, + 0x7D29BAD68B574D0BL /* 364 */, 0x81BB613E25E6FE5BL /* 365 */, + 0x071C9C10BC07913FL /* 366 */, 0xC7BEEB7909AC2D97L /* 367 */, + 0xC3E58D353BC5D757L /* 368 */, 0xEB017892F38F61E8L /* 369 */, + 0xD4EFFB9C9B1CC21AL /* 370 */, 0x99727D26F494F7ABL /* 371 */, + 0xA3E063A2956B3E03L /* 372 */, 0x9D4A8B9A4AA09C30L /* 373 */, + 0x3F6AB7D500090FB4L /* 374 */, 0x9CC0F2A057268AC0L /* 375 */, + 0x3DEE9D2DEDBF42D1L /* 376 */, 0x330F49C87960A972L /* 377 */, + 0xC6B2720287421B41L /* 378 */, 0x0AC59EC07C00369CL /* 379 */, + 0xEF4EAC49CB353425L /* 380 */, 0xF450244EEF0129D8L /* 381 */, + 0x8ACC46E5CAF4DEB6L /* 382 */, 0x2FFEAB63989263F7L /* 383 */, + 0x8F7CB9FE5D7A4578L /* 384 */, 0x5BD8F7644E634635L /* 385 */, + 0x427A7315BF2DC900L /* 386 */, 0x17D0C4AA2125261CL /* 387 */, + 0x3992486C93518E50L /* 388 */, 0xB4CBFEE0A2D7D4C3L /* 389 */, + 0x7C75D6202C5DDD8DL /* 390 */, 0xDBC295D8E35B6C61L /* 391 */, + 0x60B369D302032B19L /* 392 */, 0xCE42685FDCE44132L /* 393 */, + 0x06F3DDB9DDF65610L /* 394 */, 0x8EA4D21DB5E148F0L /* 395 */, + 0x20B0FCE62FCD496FL /* 396 */, 0x2C1B912358B0EE31L /* 397 */, + 0xB28317B818F5A308L /* 398 */, 0xA89C1E189CA6D2CFL /* 399 */, + 0x0C6B18576AAADBC8L /* 400 */, 0xB65DEAA91299FAE3L /* 401 */, + 0xFB2B794B7F1027E7L /* 402 */, 0x04E4317F443B5BEBL /* 403 */, + 0x4B852D325939D0A6L /* 404 */, 0xD5AE6BEEFB207FFCL /* 405 */, + 0x309682B281C7D374L /* 406 */, 0xBAE309A194C3B475L /* 407 */, + 0x8CC3F97B13B49F05L /* 408 */, 0x98A9422FF8293967L /* 409 */, + 0x244B16B01076FF7CL /* 410 */, 0xF8BF571C663D67EEL /* 411 */, + 0x1F0D6758EEE30DA1L /* 412 */, 0xC9B611D97ADEB9B7L /* 413 */, + 0xB7AFD5887B6C57A2L /* 414 */, 0x6290AE846B984FE1L /* 415 */, + 0x94DF4CDEACC1A5FDL /* 416 */, 0x058A5BD1C5483AFFL /* 417 */, + 0x63166CC142BA3C37L /* 418 */, 0x8DB8526EB2F76F40L /* 419 */, + 0xE10880036F0D6D4EL /* 420 */, 0x9E0523C9971D311DL /* 421 */, + 0x45EC2824CC7CD691L /* 422 */, 0x575B8359E62382C9L /* 423 */, + 0xFA9E400DC4889995L /* 424 */, 0xD1823ECB45721568L /* 425 */, + 0xDAFD983B8206082FL /* 426 */, 0xAA7D29082386A8CBL /* 427 */, + 0x269FCD4403B87588L /* 428 */, 0x1B91F5F728BDD1E0L /* 429 */, + 0xE4669F39040201F6L /* 430 */, 0x7A1D7C218CF04ADEL /* 431 */, + 0x65623C29D79CE5CEL /* 432 */, 0x2368449096C00BB1L /* 433 */, + 0xAB9BF1879DA503BAL /* 434 */, 0xBC23ECB1A458058EL /* 435 */, + 0x9A58DF01BB401ECCL /* 436 */, 0xA070E868A85F143DL /* 437 */, + 0x4FF188307DF2239EL /* 438 */, 0x14D565B41A641183L /* 439 */, + 0xEE13337452701602L /* 440 */, 0x950E3DCF3F285E09L /* 441 */, + 0x59930254B9C80953L /* 442 */, 0x3BF299408930DA6DL /* 443 */, + 0xA955943F53691387L /* 444 */, 0xA15EDECAA9CB8784L /* 445 */, + 0x29142127352BE9A0L /* 446 */, 0x76F0371FFF4E7AFBL /* 447 */, + 0x0239F450274F2228L /* 448 */, 0xBB073AF01D5E868BL /* 449 */, + 0xBFC80571C10E96C1L /* 450 */, 0xD267088568222E23L /* 451 */, + 0x9671A3D48E80B5B0L /* 452 */, 0x55B5D38AE193BB81L /* 453 */, + 0x693AE2D0A18B04B8L /* 454 */, 0x5C48B4ECADD5335FL /* 455 */, + 0xFD743B194916A1CAL /* 456 */, 0x2577018134BE98C4L /* 457 */, + 0xE77987E83C54A4ADL /* 458 */, 0x28E11014DA33E1B9L /* 459 */, + 0x270CC59E226AA213L /* 460 */, 0x71495F756D1A5F60L /* 461 */, + 0x9BE853FB60AFEF77L /* 462 */, 0xADC786A7F7443DBFL /* 463 */, + 0x0904456173B29A82L /* 464 */, 0x58BC7A66C232BD5EL /* 465 */, + 0xF306558C673AC8B2L /* 466 */, 0x41F639C6B6C9772AL /* 467 */, + 0x216DEFE99FDA35DAL /* 468 */, 0x11640CC71C7BE615L /* 469 */, + 0x93C43694565C5527L /* 470 */, 0xEA038E6246777839L /* 471 */, + 0xF9ABF3CE5A3E2469L /* 472 */, 0x741E768D0FD312D2L /* 473 */, + 0x0144B883CED652C6L /* 474 */, 0xC20B5A5BA33F8552L /* 475 */, + 0x1AE69633C3435A9DL /* 476 */, 0x97A28CA4088CFDECL /* 477 */, + 0x8824A43C1E96F420L /* 478 */, 0x37612FA66EEEA746L /* 479 */, + 0x6B4CB165F9CF0E5AL /* 480 */, 0x43AA1C06A0ABFB4AL /* 481 */, + 0x7F4DC26FF162796BL /* 482 */, 0x6CBACC8E54ED9B0FL /* 483 */, + 0xA6B7FFEFD2BB253EL /* 484 */, 0x2E25BC95B0A29D4FL /* 485 */, + 0x86D6A58BDEF1388CL /* 486 */, 0xDED74AC576B6F054L /* 487 */, + 0x8030BDBC2B45805DL /* 488 */, 0x3C81AF70E94D9289L /* 489 */, + 0x3EFF6DDA9E3100DBL /* 490 */, 0xB38DC39FDFCC8847L /* 491 */, + 0x123885528D17B87EL /* 492 */, 0xF2DA0ED240B1B642L /* 493 */, + 0x44CEFADCD54BF9A9L /* 494 */, 0x1312200E433C7EE6L /* 495 */, + 0x9FFCC84F3A78C748L /* 496 */, 0xF0CD1F72248576BBL /* 497 */, + 0xEC6974053638CFE4L /* 498 */, 0x2BA7B67C0CEC4E4CL /* 499 */, + 0xAC2F4DF3E5CE32EDL /* 500 */, 0xCB33D14326EA4C11L /* 501 */, + 0xA4E9044CC77E58BCL /* 502 */, 0x5F513293D934FCEFL /* 503 */, + 0x5DC9645506E55444L /* 504 */, 0x50DE418F317DE40AL /* 505 */, + 0x388CB31A69DDE259L /* 506 */, 0x2DB4A83455820A86L /* 507 */, + 0x9010A91E84711AE9L /* 508 */, 0x4DF7F0B7B1498371L /* 509 */, + 0xD62A2EABC0977179L /* 510 */, 0x22FAC097AA8D5C0EL /* 511 */, + }; + + private static final long[] t3 = { + 0xF49FCC2FF1DAF39BL /* 512 */, 0x487FD5C66FF29281L /* 513 */, + 0xE8A30667FCDCA83FL /* 514 */, 0x2C9B4BE3D2FCCE63L /* 515 */, + 0xDA3FF74B93FBBBC2L /* 516 */, 0x2FA165D2FE70BA66L /* 517 */, + 0xA103E279970E93D4L /* 518 */, 0xBECDEC77B0E45E71L /* 519 */, + 0xCFB41E723985E497L /* 520 */, 0xB70AAA025EF75017L /* 521 */, + 0xD42309F03840B8E0L /* 522 */, 0x8EFC1AD035898579L /* 523 */, + 0x96C6920BE2B2ABC5L /* 524 */, 0x66AF4163375A9172L /* 525 */, + 0x2174ABDCCA7127FBL /* 526 */, 0xB33CCEA64A72FF41L /* 527 */, + 0xF04A4933083066A5L /* 528 */, 0x8D970ACDD7289AF5L /* 529 */, + 0x8F96E8E031C8C25EL /* 530 */, 0xF3FEC02276875D47L /* 531 */, + 0xEC7BF310056190DDL /* 532 */, 0xF5ADB0AEBB0F1491L /* 533 */, + 0x9B50F8850FD58892L /* 534 */, 0x4975488358B74DE8L /* 535 */, + 0xA3354FF691531C61L /* 536 */, 0x0702BBE481D2C6EEL /* 537 */, + 0x89FB24057DEDED98L /* 538 */, 0xAC3075138596E902L /* 539 */, + 0x1D2D3580172772EDL /* 540 */, 0xEB738FC28E6BC30DL /* 541 */, + 0x5854EF8F63044326L /* 542 */, 0x9E5C52325ADD3BBEL /* 543 */, + 0x90AA53CF325C4623L /* 544 */, 0xC1D24D51349DD067L /* 545 */, + 0x2051CFEEA69EA624L /* 546 */, 0x13220F0A862E7E4FL /* 547 */, + 0xCE39399404E04864L /* 548 */, 0xD9C42CA47086FCB7L /* 549 */, + 0x685AD2238A03E7CCL /* 550 */, 0x066484B2AB2FF1DBL /* 551 */, + 0xFE9D5D70EFBF79ECL /* 552 */, 0x5B13B9DD9C481854L /* 553 */, + 0x15F0D475ED1509ADL /* 554 */, 0x0BEBCD060EC79851L /* 555 */, + 0xD58C6791183AB7F8L /* 556 */, 0xD1187C5052F3EEE4L /* 557 */, + 0xC95D1192E54E82FFL /* 558 */, 0x86EEA14CB9AC6CA2L /* 559 */, + 0x3485BEB153677D5DL /* 560 */, 0xDD191D781F8C492AL /* 561 */, + 0xF60866BAA784EBF9L /* 562 */, 0x518F643BA2D08C74L /* 563 */, + 0x8852E956E1087C22L /* 564 */, 0xA768CB8DC410AE8DL /* 565 */, + 0x38047726BFEC8E1AL /* 566 */, 0xA67738B4CD3B45AAL /* 567 */, + 0xAD16691CEC0DDE19L /* 568 */, 0xC6D4319380462E07L /* 569 */, + 0xC5A5876D0BA61938L /* 570 */, 0x16B9FA1FA58FD840L /* 571 */, + 0x188AB1173CA74F18L /* 572 */, 0xABDA2F98C99C021FL /* 573 */, + 0x3E0580AB134AE816L /* 574 */, 0x5F3B05B773645ABBL /* 575 */, + 0x2501A2BE5575F2F6L /* 576 */, 0x1B2F74004E7E8BA9L /* 577 */, + 0x1CD7580371E8D953L /* 578 */, 0x7F6ED89562764E30L /* 579 */, + 0xB15926FF596F003DL /* 580 */, 0x9F65293DA8C5D6B9L /* 581 */, + 0x6ECEF04DD690F84CL /* 582 */, 0x4782275FFF33AF88L /* 583 */, + 0xE41433083F820801L /* 584 */, 0xFD0DFE409A1AF9B5L /* 585 */, + 0x4325A3342CDB396BL /* 586 */, 0x8AE77E62B301B252L /* 587 */, + 0xC36F9E9F6655615AL /* 588 */, 0x85455A2D92D32C09L /* 589 */, + 0xF2C7DEA949477485L /* 590 */, 0x63CFB4C133A39EBAL /* 591 */, + 0x83B040CC6EBC5462L /* 592 */, 0x3B9454C8FDB326B0L /* 593 */, + 0x56F56A9E87FFD78CL /* 594 */, 0x2DC2940D99F42BC6L /* 595 */, + 0x98F7DF096B096E2DL /* 596 */, 0x19A6E01E3AD852BFL /* 597 */, + 0x42A99CCBDBD4B40BL /* 598 */, 0xA59998AF45E9C559L /* 599 */, + 0x366295E807D93186L /* 600 */, 0x6B48181BFAA1F773L /* 601 */, + 0x1FEC57E2157A0A1DL /* 602 */, 0x4667446AF6201AD5L /* 603 */, + 0xE615EBCACFB0F075L /* 604 */, 0xB8F31F4F68290778L /* 605 */, + 0x22713ED6CE22D11EL /* 606 */, 0x3057C1A72EC3C93BL /* 607 */, + 0xCB46ACC37C3F1F2FL /* 608 */, 0xDBB893FD02AAF50EL /* 609 */, + 0x331FD92E600B9FCFL /* 610 */, 0xA498F96148EA3AD6L /* 611 */, + 0xA8D8426E8B6A83EAL /* 612 */, 0xA089B274B7735CDCL /* 613 */, + 0x87F6B3731E524A11L /* 614 */, 0x118808E5CBC96749L /* 615 */, + 0x9906E4C7B19BD394L /* 616 */, 0xAFED7F7E9B24A20CL /* 617 */, + 0x6509EADEEB3644A7L /* 618 */, 0x6C1EF1D3E8EF0EDEL /* 619 */, + 0xB9C97D43E9798FB4L /* 620 */, 0xA2F2D784740C28A3L /* 621 */, + 0x7B8496476197566FL /* 622 */, 0x7A5BE3E6B65F069DL /* 623 */, + 0xF96330ED78BE6F10L /* 624 */, 0xEEE60DE77A076A15L /* 625 */, + 0x2B4BEE4AA08B9BD0L /* 626 */, 0x6A56A63EC7B8894EL /* 627 */, + 0x02121359BA34FEF4L /* 628 */, 0x4CBF99F8283703FCL /* 629 */, + 0x398071350CAF30C8L /* 630 */, 0xD0A77A89F017687AL /* 631 */, + 0xF1C1A9EB9E423569L /* 632 */, 0x8C7976282DEE8199L /* 633 */, + 0x5D1737A5DD1F7ABDL /* 634 */, 0x4F53433C09A9FA80L /* 635 */, + 0xFA8B0C53DF7CA1D9L /* 636 */, 0x3FD9DCBC886CCB77L /* 637 */, + 0xC040917CA91B4720L /* 638 */, 0x7DD00142F9D1DCDFL /* 639 */, + 0x8476FC1D4F387B58L /* 640 */, 0x23F8E7C5F3316503L /* 641 */, + 0x032A2244E7E37339L /* 642 */, 0x5C87A5D750F5A74BL /* 643 */, + 0x082B4CC43698992EL /* 644 */, 0xDF917BECB858F63CL /* 645 */, + 0x3270B8FC5BF86DDAL /* 646 */, 0x10AE72BB29B5DD76L /* 647 */, + 0x576AC94E7700362BL /* 648 */, 0x1AD112DAC61EFB8FL /* 649 */, + 0x691BC30EC5FAA427L /* 650 */, 0xFF246311CC327143L /* 651 */, + 0x3142368E30E53206L /* 652 */, 0x71380E31E02CA396L /* 653 */, + 0x958D5C960AAD76F1L /* 654 */, 0xF8D6F430C16DA536L /* 655 */, + 0xC8FFD13F1BE7E1D2L /* 656 */, 0x7578AE66004DDBE1L /* 657 */, + 0x05833F01067BE646L /* 658 */, 0xBB34B5AD3BFE586DL /* 659 */, + 0x095F34C9A12B97F0L /* 660 */, 0x247AB64525D60CA8L /* 661 */, + 0xDCDBC6F3017477D1L /* 662 */, 0x4A2E14D4DECAD24DL /* 663 */, + 0xBDB5E6D9BE0A1EEBL /* 664 */, 0x2A7E70F7794301ABL /* 665 */, + 0xDEF42D8A270540FDL /* 666 */, 0x01078EC0A34C22C1L /* 667 */, + 0xE5DE511AF4C16387L /* 668 */, 0x7EBB3A52BD9A330AL /* 669 */, + 0x77697857AA7D6435L /* 670 */, 0x004E831603AE4C32L /* 671 */, + 0xE7A21020AD78E312L /* 672 */, 0x9D41A70C6AB420F2L /* 673 */, + 0x28E06C18EA1141E6L /* 674 */, 0xD2B28CBD984F6B28L /* 675 */, + 0x26B75F6C446E9D83L /* 676 */, 0xBA47568C4D418D7FL /* 677 */, + 0xD80BADBFE6183D8EL /* 678 */, 0x0E206D7F5F166044L /* 679 */, + 0xE258A43911CBCA3EL /* 680 */, 0x723A1746B21DC0BCL /* 681 */, + 0xC7CAA854F5D7CDD3L /* 682 */, 0x7CAC32883D261D9CL /* 683 */, + 0x7690C26423BA942CL /* 684 */, 0x17E55524478042B8L /* 685 */, + 0xE0BE477656A2389FL /* 686 */, 0x4D289B5E67AB2DA0L /* 687 */, + 0x44862B9C8FBBFD31L /* 688 */, 0xB47CC8049D141365L /* 689 */, + 0x822C1B362B91C793L /* 690 */, 0x4EB14655FB13DFD8L /* 691 */, + 0x1ECBBA0714E2A97BL /* 692 */, 0x6143459D5CDE5F14L /* 693 */, + 0x53A8FBF1D5F0AC89L /* 694 */, 0x97EA04D81C5E5B00L /* 695 */, + 0x622181A8D4FDB3F3L /* 696 */, 0xE9BCD341572A1208L /* 697 */, + 0x1411258643CCE58AL /* 698 */, 0x9144C5FEA4C6E0A4L /* 699 */, + 0x0D33D06565CF620FL /* 700 */, 0x54A48D489F219CA1L /* 701 */, + 0xC43E5EAC6D63C821L /* 702 */, 0xA9728B3A72770DAFL /* 703 */, + 0xD7934E7B20DF87EFL /* 704 */, 0xE35503B61A3E86E5L /* 705 */, + 0xCAE321FBC819D504L /* 706 */, 0x129A50B3AC60BFA6L /* 707 */, + 0xCD5E68EA7E9FB6C3L /* 708 */, 0xB01C90199483B1C7L /* 709 */, + 0x3DE93CD5C295376CL /* 710 */, 0xAED52EDF2AB9AD13L /* 711 */, + 0x2E60F512C0A07884L /* 712 */, 0xBC3D86A3E36210C9L /* 713 */, + 0x35269D9B163951CEL /* 714 */, 0x0C7D6E2AD0CDB5FAL /* 715 */, + 0x59E86297D87F5733L /* 716 */, 0x298EF221898DB0E7L /* 717 */, + 0x55000029D1A5AA7EL /* 718 */, 0x8BC08AE1B5061B45L /* 719 */, + 0xC2C31C2B6C92703AL /* 720 */, 0x94CC596BAF25EF42L /* 721 */, + 0x0A1D73DB22540456L /* 722 */, 0x04B6A0F9D9C4179AL /* 723 */, + 0xEFFDAFA2AE3D3C60L /* 724 */, 0xF7C8075BB49496C4L /* 725 */, + 0x9CC5C7141D1CD4E3L /* 726 */, 0x78BD1638218E5534L /* 727 */, + 0xB2F11568F850246AL /* 728 */, 0xEDFABCFA9502BC29L /* 729 */, + 0x796CE5F2DA23051BL /* 730 */, 0xAAE128B0DC93537CL /* 731 */, + 0x3A493DA0EE4B29AEL /* 732 */, 0xB5DF6B2C416895D7L /* 733 */, + 0xFCABBD25122D7F37L /* 734 */, 0x70810B58105DC4B1L /* 735 */, + 0xE10FDD37F7882A90L /* 736 */, 0x524DCAB5518A3F5CL /* 737 */, + 0x3C9E85878451255BL /* 738 */, 0x4029828119BD34E2L /* 739 */, + 0x74A05B6F5D3CECCBL /* 740 */, 0xB610021542E13ECAL /* 741 */, + 0x0FF979D12F59E2ACL /* 742 */, 0x6037DA27E4F9CC50L /* 743 */, + 0x5E92975A0DF1847DL /* 744 */, 0xD66DE190D3E623FEL /* 745 */, + 0x5032D6B87B568048L /* 746 */, 0x9A36B7CE8235216EL /* 747 */, + 0x80272A7A24F64B4AL /* 748 */, 0x93EFED8B8C6916F7L /* 749 */, + 0x37DDBFF44CCE1555L /* 750 */, 0x4B95DB5D4B99BD25L /* 751 */, + 0x92D3FDA169812FC0L /* 752 */, 0xFB1A4A9A90660BB6L /* 753 */, + 0x730C196946A4B9B2L /* 754 */, 0x81E289AA7F49DA68L /* 755 */, + 0x64669A0F83B1A05FL /* 756 */, 0x27B3FF7D9644F48BL /* 757 */, + 0xCC6B615C8DB675B3L /* 758 */, 0x674F20B9BCEBBE95L /* 759 */, + 0x6F31238275655982L /* 760 */, 0x5AE488713E45CF05L /* 761 */, + 0xBF619F9954C21157L /* 762 */, 0xEABAC46040A8EAE9L /* 763 */, + 0x454C6FE9F2C0C1CDL /* 764 */, 0x419CF6496412691CL /* 765 */, + 0xD3DC3BEF265B0F70L /* 766 */, 0x6D0E60F5C3578A9EL /* 767 */, + }; + + private static final long[] t4 = { + 0x5B0E608526323C55L /* 768 */, 0x1A46C1A9FA1B59F5L /* 769 */, + 0xA9E245A17C4C8FFAL /* 770 */, 0x65CA5159DB2955D7L /* 771 */, + 0x05DB0A76CE35AFC2L /* 772 */, 0x81EAC77EA9113D45L /* 773 */, + 0x528EF88AB6AC0A0DL /* 774 */, 0xA09EA253597BE3FFL /* 775 */, + 0x430DDFB3AC48CD56L /* 776 */, 0xC4B3A67AF45CE46FL /* 777 */, + 0x4ECECFD8FBE2D05EL /* 778 */, 0x3EF56F10B39935F0L /* 779 */, + 0x0B22D6829CD619C6L /* 780 */, 0x17FD460A74DF2069L /* 781 */, + 0x6CF8CC8E8510ED40L /* 782 */, 0xD6C824BF3A6ECAA7L /* 783 */, + 0x61243D581A817049L /* 784 */, 0x048BACB6BBC163A2L /* 785 */, + 0xD9A38AC27D44CC32L /* 786 */, 0x7FDDFF5BAAF410ABL /* 787 */, + 0xAD6D495AA804824BL /* 788 */, 0xE1A6A74F2D8C9F94L /* 789 */, + 0xD4F7851235DEE8E3L /* 790 */, 0xFD4B7F886540D893L /* 791 */, + 0x247C20042AA4BFDAL /* 792 */, 0x096EA1C517D1327CL /* 793 */, + 0xD56966B4361A6685L /* 794 */, 0x277DA5C31221057DL /* 795 */, + 0x94D59893A43ACFF7L /* 796 */, 0x64F0C51CCDC02281L /* 797 */, + 0x3D33BCC4FF6189DBL /* 798 */, 0xE005CB184CE66AF1L /* 799 */, + 0xFF5CCD1D1DB99BEAL /* 800 */, 0xB0B854A7FE42980FL /* 801 */, + 0x7BD46A6A718D4B9FL /* 802 */, 0xD10FA8CC22A5FD8CL /* 803 */, + 0xD31484952BE4BD31L /* 804 */, 0xC7FA975FCB243847L /* 805 */, + 0x4886ED1E5846C407L /* 806 */, 0x28CDDB791EB70B04L /* 807 */, + 0xC2B00BE2F573417FL /* 808 */, 0x5C9590452180F877L /* 809 */, + 0x7A6BDDFFF370EB00L /* 810 */, 0xCE509E38D6D9D6A4L /* 811 */, + 0xEBEB0F00647FA702L /* 812 */, 0x1DCC06CF76606F06L /* 813 */, + 0xE4D9F28BA286FF0AL /* 814 */, 0xD85A305DC918C262L /* 815 */, + 0x475B1D8732225F54L /* 816 */, 0x2D4FB51668CCB5FEL /* 817 */, + 0xA679B9D9D72BBA20L /* 818 */, 0x53841C0D912D43A5L /* 819 */, + 0x3B7EAA48BF12A4E8L /* 820 */, 0x781E0E47F22F1DDFL /* 821 */, + 0xEFF20CE60AB50973L /* 822 */, 0x20D261D19DFFB742L /* 823 */, + 0x16A12B03062A2E39L /* 824 */, 0x1960EB2239650495L /* 825 */, + 0x251C16FED50EB8B8L /* 826 */, 0x9AC0C330F826016EL /* 827 */, + 0xED152665953E7671L /* 828 */, 0x02D63194A6369570L /* 829 */, + 0x5074F08394B1C987L /* 830 */, 0x70BA598C90B25CE1L /* 831 */, + 0x794A15810B9742F6L /* 832 */, 0x0D5925E9FCAF8C6CL /* 833 */, + 0x3067716CD868744EL /* 834 */, 0x910AB077E8D7731BL /* 835 */, + 0x6A61BBDB5AC42F61L /* 836 */, 0x93513EFBF0851567L /* 837 */, + 0xF494724B9E83E9D5L /* 838 */, 0xE887E1985C09648DL /* 839 */, + 0x34B1D3C675370CFDL /* 840 */, 0xDC35E433BC0D255DL /* 841 */, + 0xD0AAB84234131BE0L /* 842 */, 0x08042A50B48B7EAFL /* 843 */, + 0x9997C4EE44A3AB35L /* 844 */, 0x829A7B49201799D0L /* 845 */, + 0x263B8307B7C54441L /* 846 */, 0x752F95F4FD6A6CA6L /* 847 */, + 0x927217402C08C6E5L /* 848 */, 0x2A8AB754A795D9EEL /* 849 */, + 0xA442F7552F72943DL /* 850 */, 0x2C31334E19781208L /* 851 */, + 0x4FA98D7CEAEE6291L /* 852 */, 0x55C3862F665DB309L /* 853 */, + 0xBD0610175D53B1F3L /* 854 */, 0x46FE6CB840413F27L /* 855 */, + 0x3FE03792DF0CFA59L /* 856 */, 0xCFE700372EB85E8FL /* 857 */, + 0xA7BE29E7ADBCE118L /* 858 */, 0xE544EE5CDE8431DDL /* 859 */, + 0x8A781B1B41F1873EL /* 860 */, 0xA5C94C78A0D2F0E7L /* 861 */, + 0x39412E2877B60728L /* 862 */, 0xA1265EF3AFC9A62CL /* 863 */, + 0xBCC2770C6A2506C5L /* 864 */, 0x3AB66DD5DCE1CE12L /* 865 */, + 0xE65499D04A675B37L /* 866 */, 0x7D8F523481BFD216L /* 867 */, + 0x0F6F64FCEC15F389L /* 868 */, 0x74EFBE618B5B13C8L /* 869 */, + 0xACDC82B714273E1DL /* 870 */, 0xDD40BFE003199D17L /* 871 */, + 0x37E99257E7E061F8L /* 872 */, 0xFA52626904775AAAL /* 873 */, + 0x8BBBF63A463D56F9L /* 874 */, 0xF0013F1543A26E64L /* 875 */, + 0xA8307E9F879EC898L /* 876 */, 0xCC4C27A4150177CCL /* 877 */, + 0x1B432F2CCA1D3348L /* 878 */, 0xDE1D1F8F9F6FA013L /* 879 */, + 0x606602A047A7DDD6L /* 880 */, 0xD237AB64CC1CB2C7L /* 881 */, + 0x9B938E7225FCD1D3L /* 882 */, 0xEC4E03708E0FF476L /* 883 */, + 0xFEB2FBDA3D03C12DL /* 884 */, 0xAE0BCED2EE43889AL /* 885 */, + 0x22CB8923EBFB4F43L /* 886 */, 0x69360D013CF7396DL /* 887 */, + 0x855E3602D2D4E022L /* 888 */, 0x073805BAD01F784CL /* 889 */, + 0x33E17A133852F546L /* 890 */, 0xDF4874058AC7B638L /* 891 */, + 0xBA92B29C678AA14AL /* 892 */, 0x0CE89FC76CFAADCDL /* 893 */, + 0x5F9D4E0908339E34L /* 894 */, 0xF1AFE9291F5923B9L /* 895 */, + 0x6E3480F60F4A265FL /* 896 */, 0xEEBF3A2AB29B841CL /* 897 */, + 0xE21938A88F91B4ADL /* 898 */, 0x57DFEFF845C6D3C3L /* 899 */, + 0x2F006B0BF62CAAF2L /* 900 */, 0x62F479EF6F75EE78L /* 901 */, + 0x11A55AD41C8916A9L /* 902 */, 0xF229D29084FED453L /* 903 */, + 0x42F1C27B16B000E6L /* 904 */, 0x2B1F76749823C074L /* 905 */, + 0x4B76ECA3C2745360L /* 906 */, 0x8C98F463B91691BDL /* 907 */, + 0x14BCC93CF1ADE66AL /* 908 */, 0x8885213E6D458397L /* 909 */, + 0x8E177DF0274D4711L /* 910 */, 0xB49B73B5503F2951L /* 911 */, + 0x10168168C3F96B6BL /* 912 */, 0x0E3D963B63CAB0AEL /* 913 */, + 0x8DFC4B5655A1DB14L /* 914 */, 0xF789F1356E14DE5CL /* 915 */, + 0x683E68AF4E51DAC1L /* 916 */, 0xC9A84F9D8D4B0FD9L /* 917 */, + 0x3691E03F52A0F9D1L /* 918 */, 0x5ED86E46E1878E80L /* 919 */, + 0x3C711A0E99D07150L /* 920 */, 0x5A0865B20C4E9310L /* 921 */, + 0x56FBFC1FE4F0682EL /* 922 */, 0xEA8D5DE3105EDF9BL /* 923 */, + 0x71ABFDB12379187AL /* 924 */, 0x2EB99DE1BEE77B9CL /* 925 */, + 0x21ECC0EA33CF4523L /* 926 */, 0x59A4D7521805C7A1L /* 927 */, + 0x3896F5EB56AE7C72L /* 928 */, 0xAA638F3DB18F75DCL /* 929 */, + 0x9F39358DABE9808EL /* 930 */, 0xB7DEFA91C00B72ACL /* 931 */, + 0x6B5541FD62492D92L /* 932 */, 0x6DC6DEE8F92E4D5BL /* 933 */, + 0x353F57ABC4BEEA7EL /* 934 */, 0x735769D6DA5690CEL /* 935 */, + 0x0A234AA642391484L /* 936 */, 0xF6F9508028F80D9DL /* 937 */, + 0xB8E319A27AB3F215L /* 938 */, 0x31AD9C1151341A4DL /* 939 */, + 0x773C22A57BEF5805L /* 940 */, 0x45C7561A07968633L /* 941 */, + 0xF913DA9E249DBE36L /* 942 */, 0xDA652D9B78A64C68L /* 943 */, + 0x4C27A97F3BC334EFL /* 944 */, 0x76621220E66B17F4L /* 945 */, + 0x967743899ACD7D0BL /* 946 */, 0xF3EE5BCAE0ED6782L /* 947 */, + 0x409F753600C879FCL /* 948 */, 0x06D09A39B5926DB6L /* 949 */, + 0x6F83AEB0317AC588L /* 950 */, 0x01E6CA4A86381F21L /* 951 */, + 0x66FF3462D19F3025L /* 952 */, 0x72207C24DDFD3BFBL /* 953 */, + 0x4AF6B6D3E2ECE2EBL /* 954 */, 0x9C994DBEC7EA08DEL /* 955 */, + 0x49ACE597B09A8BC4L /* 956 */, 0xB38C4766CF0797BAL /* 957 */, + 0x131B9373C57C2A75L /* 958 */, 0xB1822CCE61931E58L /* 959 */, + 0x9D7555B909BA1C0CL /* 960 */, 0x127FAFDD937D11D2L /* 961 */, + 0x29DA3BADC66D92E4L /* 962 */, 0xA2C1D57154C2ECBCL /* 963 */, + 0x58C5134D82F6FE24L /* 964 */, 0x1C3AE3515B62274FL /* 965 */, + 0xE907C82E01CB8126L /* 966 */, 0xF8ED091913E37FCBL /* 967 */, + 0x3249D8F9C80046C9L /* 968 */, 0x80CF9BEDE388FB63L /* 969 */, + 0x1881539A116CF19EL /* 970 */, 0x5103F3F76BD52457L /* 971 */, + 0x15B7E6F5AE47F7A8L /* 972 */, 0xDBD7C6DED47E9CCFL /* 973 */, + 0x44E55C410228BB1AL /* 974 */, 0xB647D4255EDB4E99L /* 975 */, + 0x5D11882BB8AAFC30L /* 976 */, 0xF5098BBB29D3212AL /* 977 */, + 0x8FB5EA14E90296B3L /* 978 */, 0x677B942157DD025AL /* 979 */, + 0xFB58E7C0A390ACB5L /* 980 */, 0x89D3674C83BD4A01L /* 981 */, + 0x9E2DA4DF4BF3B93BL /* 982 */, 0xFCC41E328CAB4829L /* 983 */, + 0x03F38C96BA582C52L /* 984 */, 0xCAD1BDBD7FD85DB2L /* 985 */, + 0xBBB442C16082AE83L /* 986 */, 0xB95FE86BA5DA9AB0L /* 987 */, + 0xB22E04673771A93FL /* 988 */, 0x845358C9493152D8L /* 989 */, + 0xBE2A488697B4541EL /* 990 */, 0x95A2DC2DD38E6966L /* 991 */, + 0xC02C11AC923C852BL /* 992 */, 0x2388B1990DF2A87BL /* 993 */, + 0x7C8008FA1B4F37BEL /* 994 */, 0x1F70D0C84D54E503L /* 995 */, + 0x5490ADEC7ECE57D4L /* 996 */, 0x002B3C27D9063A3AL /* 997 */, + 0x7EAEA3848030A2BFL /* 998 */, 0xC602326DED2003C0L /* 999 */, + 0x83A7287D69A94086L /* 1000 */, 0xC57A5FCB30F57A8AL /* 1001 */, + 0xB56844E479EBE779L /* 1002 */, 0xA373B40F05DCBCE9L /* 1003 */, + 0xD71A786E88570EE2L /* 1004 */, 0x879CBACDBDE8F6A0L /* 1005 */, + 0x976AD1BCC164A32FL /* 1006 */, 0xAB21E25E9666D78BL /* 1007 */, + 0x901063AAE5E5C33CL /* 1008 */, 0x9818B34448698D90L /* 1009 */, + 0xE36487AE3E1E8ABBL /* 1010 */, 0xAFBDF931893BDCB4L /* 1011 */, + 0x6345A0DC5FBBD519L /* 1012 */, 0x8628FE269B9465CAL /* 1013 */, + 0x1E5D01603F9C51ECL /* 1014 */, 0x4DE44006A15049B7L /* 1015 */, + 0xBF6C70E5F776CBB1L /* 1016 */, 0x411218F2EF552BEDL /* 1017 */, + 0xCB0C0708705A36A3L /* 1018 */, 0xE74D14754F986044L /* 1019 */, + 0xCD56D9430EA8280EL /* 1020 */, 0xC12591D7535F5065L /* 1021 */, + 0xC83223F1720AEF96L /* 1022 */, 0xC3A0396F7363A51FL /* 1023 */ + }; + + private static final int DIGEST_LENGTH = 24; + + // + // registers + // + private long a, b, c; + private long byteCount; + + // + // buffers + // + private byte[] buf = new byte[8]; + private int bOff = 0; + + private long[] x = new long[8]; + private int xOff = 0; + + /** + * Standard constructor + */ + public TigerDigest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public TigerDigest(TigerDigest t) + { + a = t.a; + b = t.b; + c = t.c; + + System.arraycopy(t.x, 0, x, 0, t.x.length); + xOff = t.xOff; + + System.arraycopy(t.buf, 0, buf, 0, t.buf.length); + bOff = t.bOff; + + byteCount = t.byteCount; + } + + public String getAlgorithmName() + { + return "Tiger"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + private void processWord( + byte[] b, + int off) + { + x[xOff++] = ((long)(b[off + 7] & 0xff) << 56) + | ((long)(b[off + 6] & 0xff) << 48) + | ((long)(b[off + 5] & 0xff) << 40) + | ((long)(b[off + 4] & 0xff) << 32) + | ((long)(b[off + 3] & 0xff) << 24) + | ((long)(b[off + 2] & 0xff) << 16) + | ((long)(b[off + 1] & 0xff) << 8) + | ((b[off + 0] & 0xff)); + + if (xOff == x.length) + { + processBlock(); + } + + bOff = 0; + } + + public void update( + byte in) + { + buf[bOff++] = in; + + if (bOff == buf.length) + { + processWord(buf, 0); + } + + byteCount++; + } + + public void update( + byte[] in, + int inOff, + int len) + { + // + // fill the current word + // + while ((bOff != 0) && (len > 0)) + { + update(in[inOff]); + + inOff++; + len--; + } + + // + // process whole words. + // + while (len > 8) + { + processWord(in, inOff); + + inOff += 8; + len -= 8; + byteCount += 8; + } + + // + // load in the remainder. + // + while (len > 0) + { + update(in[inOff]); + + inOff++; + len--; + } + } + + private void roundABC( + long x, + long mul) + { + c ^= x ; + a -= t1[(int)c & 0xff] ^ t2[(int)(c >> 16) & 0xff] + ^ t3[(int)(c >> 32) & 0xff] ^ t4[(int)(c >> 48) & 0xff]; + b += t4[(int)(c >> 8) & 0xff] ^ t3[(int)(c >> 24) & 0xff] + ^ t2[(int)(c >> 40) & 0xff] ^ t1[(int)(c >> 56) & 0xff]; + b *= mul; + } + + private void roundBCA( + long x, + long mul) + { + a ^= x ; + b -= t1[(int)a & 0xff] ^ t2[(int)(a >> 16) & 0xff] + ^ t3[(int)(a >> 32) & 0xff] ^ t4[(int)(a >> 48) & 0xff]; + c += t4[(int)(a >> 8) & 0xff] ^ t3[(int)(a >> 24) & 0xff] + ^ t2[(int)(a >> 40) & 0xff] ^ t1[(int)(a >> 56) & 0xff]; + c *= mul; + } + + private void roundCAB( + long x, + long mul) + { + b ^= x ; + c -= t1[(int)b & 0xff] ^ t2[(int)(b >> 16) & 0xff] + ^ t3[(int)(b >> 32) & 0xff] ^ t4[(int)(b >> 48) & 0xff]; + a += t4[(int)(b >> 8) & 0xff] ^ t3[(int)(b >> 24) & 0xff] + ^ t2[(int)(b >> 40) & 0xff] ^ t1[(int)(b >> 56) & 0xff]; + a *= mul; + } + + private void keySchedule() + { + x[0] -= x[7] ^ 0xA5A5A5A5A5A5A5A5L; + x[1] ^= x[0]; + x[2] += x[1]; + x[3] -= x[2] ^ ((~x[1]) << 19); + x[4] ^= x[3]; + x[5] += x[4]; + x[6] -= x[5] ^ ((~x[4]) >>> 23); + x[7] ^= x[6]; + x[0] += x[7]; + x[1] -= x[0] ^ ((~x[7]) << 19); + x[2] ^= x[1]; + x[3] += x[2]; + x[4] -= x[3] ^ ((~x[2]) >>> 23); + x[5] ^= x[4]; + x[6] += x[5]; + x[7] -= x[6] ^ 0x0123456789ABCDEFL; + } + + private void processBlock() + { + // + // save abc + // + long aa = a; + long bb = b; + long cc = c; + + // + // rounds and schedule + // + roundABC(x[0], 5); + roundBCA(x[1], 5); + roundCAB(x[2], 5); + roundABC(x[3], 5); + roundBCA(x[4], 5); + roundCAB(x[5], 5); + roundABC(x[6], 5); + roundBCA(x[7], 5); + + keySchedule(); + + roundCAB(x[0], 7); + roundABC(x[1], 7); + roundBCA(x[2], 7); + roundCAB(x[3], 7); + roundABC(x[4], 7); + roundBCA(x[5], 7); + roundCAB(x[6], 7); + roundABC(x[7], 7); + + keySchedule(); + + roundBCA(x[0], 9); + roundCAB(x[1], 9); + roundABC(x[2], 9); + roundBCA(x[3], 9); + roundCAB(x[4], 9); + roundABC(x[5], 9); + roundBCA(x[6], 9); + roundCAB(x[7], 9); + + // + // feed forward + // + a ^= aa; + b -= bb; + c += cc; + + // + // clear the x buffer + // + xOff = 0; + for (int i = 0; i != x.length; i++) + { + x[i] = 0; + } + } + + public void unpackWord( + long r, + byte[] out, + int outOff) + { + out[outOff + 7] = (byte)(r >> 56); + out[outOff + 6] = (byte)(r >> 48); + out[outOff + 5] = (byte)(r >> 40); + out[outOff + 4] = (byte)(r >> 32); + out[outOff + 3] = (byte)(r >> 24); + out[outOff + 2] = (byte)(r >> 16); + out[outOff + 1] = (byte)(r >> 8); + out[outOff] = (byte)r; + } + + private void processLength( + long bitLength) + { + x[7] = bitLength; + } + + private void finish() + { + long bitLength = (byteCount << 3); + + update((byte)0x01); + + while (bOff != 0) + { + update((byte)0); + } + + processLength(bitLength); + + processBlock(); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(a, out, outOff); + unpackWord(b, out, outOff + 8); + unpackWord(c, out, outOff + 16); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables + */ + public void reset() + { + a = 0x0123456789ABCDEFL; + b = 0xFEDCBA9876543210L; + c = 0xF096A5B4C3B2E187L; + + xOff = 0; + for (int i = 0; i != x.length; i++) + { + x[i] = 0; + } + + bOff = 0; + for (int i = 0; i != buf.length; i++) + { + buf[i] = 0; + } + + byteCount = 0; + } + + public int getByteLength() + { + return BYTE_LENGTH; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/digests/WhirlpoolDigest.java b/src/com/google/bitcoin/bouncycastle/crypto/digests/WhirlpoolDigest.java new file mode 100644 index 000000000..5447ad85d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/digests/WhirlpoolDigest.java @@ -0,0 +1,396 @@ +package com.google.bitcoin.bouncycastle.crypto.digests; + +import com.google.bitcoin.bouncycastle.crypto.ExtendedDigest; +import com.google.bitcoin.bouncycastle.util.Arrays; + + +/** + * Implementation of WhirlpoolDigest, based on Java source published by Barreto + * and Rijmen. + * + */ +public final class WhirlpoolDigest + implements ExtendedDigest +{ + private static final int BYTE_LENGTH = 64; + + private static final int DIGEST_LENGTH_BYTES = 512 / 8; + private static final int ROUNDS = 10; + private static final int REDUCTION_POLYNOMIAL = 0x011d; // 2^8 + 2^4 + 2^3 + 2 + 1; + + private static final int[] SBOX = { + 0x18, 0x23, 0xc6, 0xe8, 0x87, 0xb8, 0x01, 0x4f, 0x36, 0xa6, 0xd2, 0xf5, 0x79, 0x6f, 0x91, 0x52, + 0x60, 0xbc, 0x9b, 0x8e, 0xa3, 0x0c, 0x7b, 0x35, 0x1d, 0xe0, 0xd7, 0xc2, 0x2e, 0x4b, 0xfe, 0x57, + 0x15, 0x77, 0x37, 0xe5, 0x9f, 0xf0, 0x4a, 0xda, 0x58, 0xc9, 0x29, 0x0a, 0xb1, 0xa0, 0x6b, 0x85, + 0xbd, 0x5d, 0x10, 0xf4, 0xcb, 0x3e, 0x05, 0x67, 0xe4, 0x27, 0x41, 0x8b, 0xa7, 0x7d, 0x95, 0xd8, + 0xfb, 0xee, 0x7c, 0x66, 0xdd, 0x17, 0x47, 0x9e, 0xca, 0x2d, 0xbf, 0x07, 0xad, 0x5a, 0x83, 0x33, + 0x63, 0x02, 0xaa, 0x71, 0xc8, 0x19, 0x49, 0xd9, 0xf2, 0xe3, 0x5b, 0x88, 0x9a, 0x26, 0x32, 0xb0, + 0xe9, 0x0f, 0xd5, 0x80, 0xbe, 0xcd, 0x34, 0x48, 0xff, 0x7a, 0x90, 0x5f, 0x20, 0x68, 0x1a, 0xae, + 0xb4, 0x54, 0x93, 0x22, 0x64, 0xf1, 0x73, 0x12, 0x40, 0x08, 0xc3, 0xec, 0xdb, 0xa1, 0x8d, 0x3d, + 0x97, 0x00, 0xcf, 0x2b, 0x76, 0x82, 0xd6, 0x1b, 0xb5, 0xaf, 0x6a, 0x50, 0x45, 0xf3, 0x30, 0xef, + 0x3f, 0x55, 0xa2, 0xea, 0x65, 0xba, 0x2f, 0xc0, 0xde, 0x1c, 0xfd, 0x4d, 0x92, 0x75, 0x06, 0x8a, + 0xb2, 0xe6, 0x0e, 0x1f, 0x62, 0xd4, 0xa8, 0x96, 0xf9, 0xc5, 0x25, 0x59, 0x84, 0x72, 0x39, 0x4c, + 0x5e, 0x78, 0x38, 0x8c, 0xd1, 0xa5, 0xe2, 0x61, 0xb3, 0x21, 0x9c, 0x1e, 0x43, 0xc7, 0xfc, 0x04, + 0x51, 0x99, 0x6d, 0x0d, 0xfa, 0xdf, 0x7e, 0x24, 0x3b, 0xab, 0xce, 0x11, 0x8f, 0x4e, 0xb7, 0xeb, + 0x3c, 0x81, 0x94, 0xf7, 0xb9, 0x13, 0x2c, 0xd3, 0xe7, 0x6e, 0xc4, 0x03, 0x56, 0x44, 0x7f, 0xa9, + 0x2a, 0xbb, 0xc1, 0x53, 0xdc, 0x0b, 0x9d, 0x6c, 0x31, 0x74, 0xf6, 0x46, 0xac, 0x89, 0x14, 0xe1, + 0x16, 0x3a, 0x69, 0x09, 0x70, 0xb6, 0xd0, 0xed, 0xcc, 0x42, 0x98, 0xa4, 0x28, 0x5c, 0xf8, 0x86 + }; + + private static final long[] C0 = new long[256]; + private static final long[] C1 = new long[256]; + private static final long[] C2 = new long[256]; + private static final long[] C3 = new long[256]; + private static final long[] C4 = new long[256]; + private static final long[] C5 = new long[256]; + private static final long[] C6 = new long[256]; + private static final long[] C7 = new long[256]; + + private final long[] _rc = new long[ROUNDS + 1]; + + public WhirlpoolDigest() + { + for (int i = 0; i < 256; i++) + { + int v1 = SBOX[i]; + int v2 = maskWithReductionPolynomial(v1 << 1); + int v4 = maskWithReductionPolynomial(v2 << 1); + int v5 = v4 ^ v1; + int v8 = maskWithReductionPolynomial(v4 << 1); + int v9 = v8 ^ v1; + + C0[i] = packIntoLong(v1, v1, v4, v1, v8, v5, v2, v9); + C1[i] = packIntoLong(v9, v1, v1, v4, v1, v8, v5, v2); + C2[i] = packIntoLong(v2, v9, v1, v1, v4, v1, v8, v5); + C3[i] = packIntoLong(v5, v2, v9, v1, v1, v4, v1, v8); + C4[i] = packIntoLong(v8, v5, v2, v9, v1, v1, v4, v1); + C5[i] = packIntoLong(v1, v8, v5, v2, v9, v1, v1, v4); + C6[i] = packIntoLong(v4, v1, v8, v5, v2, v9, v1, v1); + C7[i] = packIntoLong(v1, v4, v1, v8, v5, v2, v9, v1); + + } + + _rc[0] = 0L; + for (int r = 1; r <= ROUNDS; r++) + { + int i = 8 * (r - 1); + _rc[r] = (C0[i ] & 0xff00000000000000L) ^ + (C1[i + 1] & 0x00ff000000000000L) ^ + (C2[i + 2] & 0x0000ff0000000000L) ^ + (C3[i + 3] & 0x000000ff00000000L) ^ + (C4[i + 4] & 0x00000000ff000000L) ^ + (C5[i + 5] & 0x0000000000ff0000L) ^ + (C6[i + 6] & 0x000000000000ff00L) ^ + (C7[i + 7] & 0x00000000000000ffL); + } + + } + + private long packIntoLong(int b7, int b6, int b5, int b4, int b3, int b2, int b1, int b0) + { + return + ((long)b7 << 56) ^ + ((long)b6 << 48) ^ + ((long)b5 << 40) ^ + ((long)b4 << 32) ^ + ((long)b3 << 24) ^ + ((long)b2 << 16) ^ + ((long)b1 << 8) ^ + b0; + } + + /* + * int's are used to prevent sign extension. The values that are really being used are + * actually just 0..255 + */ + private int maskWithReductionPolynomial(int input) + { + int rv = input; + if (rv >= 0x100L) // high bit set + { + rv ^= REDUCTION_POLYNOMIAL; // reduced by the polynomial + } + return rv; + } + + // --------------------------------------------------------------------------------------// + + // -- buffer information -- + private static final int BITCOUNT_ARRAY_SIZE = 32; + private byte[] _buffer = new byte[64]; + private int _bufferPos = 0; + private short[] _bitCount = new short[BITCOUNT_ARRAY_SIZE]; + + // -- internal hash state -- + private long[] _hash = new long[8]; + private long[] _K = new long[8]; // the round key + private long[] _L = new long[8]; + private long[] _block = new long[8]; // mu (buffer) + private long[] _state = new long[8]; // the current "cipher" state + + + + /** + * Copy constructor. This will copy the state of the provided message + * digest. + */ + public WhirlpoolDigest(WhirlpoolDigest originalDigest) + { + System.arraycopy(originalDigest._rc, 0, _rc, 0, _rc.length); + + System.arraycopy(originalDigest._buffer, 0, _buffer, 0, _buffer.length); + + this._bufferPos = originalDigest._bufferPos; + System.arraycopy(originalDigest._bitCount, 0, _bitCount, 0, _bitCount.length); + + // -- internal hash state -- + System.arraycopy(originalDigest._hash, 0, _hash, 0, _hash.length); + System.arraycopy(originalDigest._K, 0, _K, 0, _K.length); + System.arraycopy(originalDigest._L, 0, _L, 0, _L.length); + System.arraycopy(originalDigest._block, 0, _block, 0, _block.length); + System.arraycopy(originalDigest._state, 0, _state, 0, _state.length); + } + + public String getAlgorithmName() + { + return "Whirlpool"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH_BYTES; + } + + public int doFinal(byte[] out, int outOff) + { + // sets out[outOff] .. out[outOff+DIGEST_LENGTH_BYTES] + finish(); + + for (int i = 0; i < 8; i++) + { + convertLongToByteArray(_hash[i], out, outOff + (i * 8)); + } + + reset(); + return getDigestSize(); + } + + /** + * reset the chaining variables + */ + public void reset() + { + // set variables to null, blank, whatever + _bufferPos = 0; + Arrays.fill(_bitCount, (short)0); + Arrays.fill(_buffer, (byte)0); + Arrays.fill(_hash, 0); + Arrays.fill(_K, 0); + Arrays.fill(_L, 0); + Arrays.fill(_block, 0); + Arrays.fill(_state, 0); + } + + // this takes a buffer of information and fills the block + private void processFilledBuffer(byte[] in, int inOff) + { + // copies into the block... + for (int i = 0; i < _state.length; i++) + { + _block[i] = bytesToLongFromBuffer(_buffer, i * 8); + } + processBlock(); + _bufferPos = 0; + Arrays.fill(_buffer, (byte)0); + } + + private long bytesToLongFromBuffer(byte[] buffer, int startPos) + { + long rv = (((buffer[startPos + 0] & 0xffL) << 56) | + ((buffer[startPos + 1] & 0xffL) << 48) | + ((buffer[startPos + 2] & 0xffL) << 40) | + ((buffer[startPos + 3] & 0xffL) << 32) | + ((buffer[startPos + 4] & 0xffL) << 24) | + ((buffer[startPos + 5] & 0xffL) << 16) | + ((buffer[startPos + 6] & 0xffL) << 8) | + ((buffer[startPos + 7]) & 0xffL)); + + return rv; + } + + private void convertLongToByteArray(long inputLong, byte[] outputArray, int offSet) + { + for (int i = 0; i < 8; i++) + { + outputArray[offSet + i] = (byte)((inputLong >> (56 - (i * 8))) & 0xff); + } + } + + protected void processBlock() + { + // buffer contents have been transferred to the _block[] array via + // processFilledBuffer + + // compute and apply K^0 + for (int i = 0; i < 8; i++) + { + _state[i] = _block[i] ^ (_K[i] = _hash[i]); + } + + // iterate over the rounds + for (int round = 1; round <= ROUNDS; round++) + { + for (int i = 0; i < 8; i++) + { + _L[i] = 0; + _L[i] ^= C0[(int)(_K[(i - 0) & 7] >>> 56) & 0xff]; + _L[i] ^= C1[(int)(_K[(i - 1) & 7] >>> 48) & 0xff]; + _L[i] ^= C2[(int)(_K[(i - 2) & 7] >>> 40) & 0xff]; + _L[i] ^= C3[(int)(_K[(i - 3) & 7] >>> 32) & 0xff]; + _L[i] ^= C4[(int)(_K[(i - 4) & 7] >>> 24) & 0xff]; + _L[i] ^= C5[(int)(_K[(i - 5) & 7] >>> 16) & 0xff]; + _L[i] ^= C6[(int)(_K[(i - 6) & 7] >>> 8) & 0xff]; + _L[i] ^= C7[(int)(_K[(i - 7) & 7]) & 0xff]; + } + + System.arraycopy(_L, 0, _K, 0, _K.length); + + _K[0] ^= _rc[round]; + + // apply the round transformation + for (int i = 0; i < 8; i++) + { + _L[i] = _K[i]; + + _L[i] ^= C0[(int)(_state[(i - 0) & 7] >>> 56) & 0xff]; + _L[i] ^= C1[(int)(_state[(i - 1) & 7] >>> 48) & 0xff]; + _L[i] ^= C2[(int)(_state[(i - 2) & 7] >>> 40) & 0xff]; + _L[i] ^= C3[(int)(_state[(i - 3) & 7] >>> 32) & 0xff]; + _L[i] ^= C4[(int)(_state[(i - 4) & 7] >>> 24) & 0xff]; + _L[i] ^= C5[(int)(_state[(i - 5) & 7] >>> 16) & 0xff]; + _L[i] ^= C6[(int)(_state[(i - 6) & 7] >>> 8) & 0xff]; + _L[i] ^= C7[(int)(_state[(i - 7) & 7]) & 0xff]; + } + + // save the current state + System.arraycopy(_L, 0, _state, 0, _state.length); + } + + // apply Miuaguchi-Preneel compression + for (int i = 0; i < 8; i++) + { + _hash[i] ^= _state[i] ^ _block[i]; + } + + } + + public void update(byte in) + { + _buffer[_bufferPos] = in; + + //System.out.println("adding to buffer = "+_buffer[_bufferPos]); + + ++_bufferPos; + + if (_bufferPos == _buffer.length) + { + processFilledBuffer(_buffer, 0); + } + + increment(); + } + + /* + * increment() can be implemented in this way using 2 arrays or + * by having some temporary variables that are used to set the + * value provided by EIGHT[i] and carry within the loop. + * + * not having done any timing, this seems likely to be faster + * at the slight expense of 32*(sizeof short) bytes + */ + private static final short[] EIGHT = new short[BITCOUNT_ARRAY_SIZE]; + static + { + EIGHT[BITCOUNT_ARRAY_SIZE - 1] = 8; + } + + private void increment() + { + int carry = 0; + for (int i = _bitCount.length - 1; i >= 0; i--) + { + int sum = (_bitCount[i] & 0xff) + EIGHT[i] + carry; + + carry = sum >>> 8; + _bitCount[i] = (short)(sum & 0xff); + } + } + + public void update(byte[] in, int inOff, int len) + { + while (len > 0) + { + update(in[inOff]); + ++inOff; + --len; + } + + } + + private void finish() + { + /* + * this makes a copy of the current bit length. at the expense of an + * object creation of 32 bytes rather than providing a _stopCounting + * boolean which was the alternative I could think of. + */ + byte[] bitLength = copyBitLength(); + + _buffer[_bufferPos++] |= 0x80; + + if (_bufferPos == _buffer.length) + { + processFilledBuffer(_buffer, 0); + } + + /* + * Final block contains + * [ ... data .... ][0][0][0][ length ] + * + * if [ length ] cannot fit. Need to create a new block. + */ + if (_bufferPos > 32) + { + while (_bufferPos != 0) + { + update((byte)0); + } + } + + while (_bufferPos <= 32) + { + update((byte)0); + } + + // copy the length information to the final 32 bytes of the + // 64 byte block.... + System.arraycopy(bitLength, 0, _buffer, 32, bitLength.length); + + processFilledBuffer(_buffer, 0); + } + + private byte[] copyBitLength() + { + byte[] rv = new byte[BITCOUNT_ARRAY_SIZE]; + for (int i = 0; i < rv.length; i++) + { + rv[i] = (byte)(_bitCount[i] & 0xff); + } + return rv; + } + + public int getByteLength() + { + return BYTE_LENGTH; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/encodings/ISO9796d1Encoding.java b/src/com/google/bitcoin/bouncycastle/crypto/encodings/ISO9796d1Encoding.java new file mode 100644 index 000000000..9644e174f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/encodings/ISO9796d1Encoding.java @@ -0,0 +1,251 @@ +package com.google.bitcoin.bouncycastle.crypto.encodings; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.crypto.params.RSAKeyParameters; + +/** + * ISO 9796-1 padding. Note in the light of recent results you should + * only use this with RSA (rather than the "simpler" Rabin keys) and you + * should never use it with anything other than a hash (ie. even if the + * message is small don't sign the message, sign it's hash) or some "random" + * value. See your favorite search engine for details. + */ +public class ISO9796d1Encoding + implements AsymmetricBlockCipher +{ + private static byte[] shadows = { 0xe, 0x3, 0x5, 0x8, 0x9, 0x4, 0x2, 0xf, + 0x0, 0xd, 0xb, 0x6, 0x7, 0xa, 0xc, 0x1 }; + private static byte[] inverse = { 0x8, 0xf, 0x6, 0x1, 0x5, 0x2, 0xb, 0xc, + 0x3, 0x4, 0xd, 0xa, 0xe, 0x9, 0x0, 0x7 }; + + private AsymmetricBlockCipher engine; + private boolean forEncryption; + private int bitSize; + private int padBits = 0; + + public ISO9796d1Encoding( + AsymmetricBlockCipher cipher) + { + this.engine = cipher; + } + + public AsymmetricBlockCipher getUnderlyingCipher() + { + return engine; + } + + public void init( + boolean forEncryption, + CipherParameters param) + { + RSAKeyParameters kParam = null; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + kParam = (RSAKeyParameters)rParam.getParameters(); + } + else + { + kParam = (RSAKeyParameters)param; + } + + engine.init(forEncryption, param); + + bitSize = kParam.getModulus().bitLength(); + + this.forEncryption = forEncryption; + } + + /** + * return the input block size. The largest message we can process + * is (key_size_in_bits + 3)/16, which in our world comes to + * key_size_in_bytes / 2. + */ + public int getInputBlockSize() + { + int baseBlockSize = engine.getInputBlockSize(); + + if (forEncryption) + { + return (baseBlockSize + 1) / 2; + } + else + { + return baseBlockSize; + } + } + + /** + * return the maximum possible size for the output. + */ + public int getOutputBlockSize() + { + int baseBlockSize = engine.getOutputBlockSize(); + + if (forEncryption) + { + return baseBlockSize; + } + else + { + return (baseBlockSize + 1) / 2; + } + } + + /** + * set the number of bits in the next message to be treated as + * pad bits. + */ + public void setPadBits( + int padBits) + { + if (padBits > 7) + { + throw new IllegalArgumentException("padBits > 7"); + } + + this.padBits = padBits; + } + + /** + * retrieve the number of pad bits in the last decoded message. + */ + public int getPadBits() + { + return padBits; + } + + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (forEncryption) + { + return encodeBlock(in, inOff, inLen); + } + else + { + return decodeBlock(in, inOff, inLen); + } + } + + private byte[] encodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + byte[] block = new byte[(bitSize + 7) / 8]; + int r = padBits + 1; + int z = inLen; + int t = (bitSize + 13) / 16; + + for (int i = 0; i < t; i += z) + { + if (i > t - z) + { + System.arraycopy(in, inOff + inLen - (t - i), + block, block.length - t, t - i); + } + else + { + System.arraycopy(in, inOff, block, block.length - (i + z), z); + } + } + + for (int i = block.length - 2 * t; i != block.length; i += 2) + { + byte val = block[block.length - t + i / 2]; + + block[i] = (byte)((shadows[(val & 0xff) >>> 4] << 4) + | shadows[val & 0x0f]); + block[i + 1] = val; + } + + block[block.length - 2 * z] ^= r; + block[block.length - 1] = (byte)((block[block.length - 1] << 4) | 0x06); + + int maxBit = (8 - (bitSize - 1) % 8); + int offSet = 0; + + if (maxBit != 8) + { + block[0] &= 0xff >>> maxBit; + block[0] |= 0x80 >>> maxBit; + } + else + { + block[0] = 0x00; + block[1] |= 0x80; + offSet = 1; + } + + return engine.processBlock(block, offSet, block.length - offSet); + } + + /** + * @exception InvalidCipherTextException if the decrypted block is not a valid ISO 9796 bit string + */ + private byte[] decodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + byte[] block = engine.processBlock(in, inOff, inLen); + int r = 1; + int t = (bitSize + 13) / 16; + + if ((block[block.length - 1] & 0x0f) != 0x6) + { + throw new InvalidCipherTextException("invalid forcing byte in block"); + } + + block[block.length - 1] = (byte)(((block[block.length - 1] & 0xff) >>> 4) | ((inverse[(block[block.length - 2] & 0xff) >> 4]) << 4)); + block[0] = (byte)((shadows[(block[1] & 0xff) >>> 4] << 4) + | shadows[block[1] & 0x0f]); + + boolean boundaryFound = false; + int boundary = 0; + + for (int i = block.length - 1; i >= block.length - 2 * t; i -= 2) + { + int val = ((shadows[(block[i] & 0xff) >>> 4] << 4) + | shadows[block[i] & 0x0f]); + + if (((block[i - 1] ^ val) & 0xff) != 0) + { + if (!boundaryFound) + { + boundaryFound = true; + r = (block[i - 1] ^ val) & 0xff; + boundary = i - 1; + } + else + { + throw new InvalidCipherTextException("invalid tsums in block"); + } + } + } + + block[boundary] = 0; + + byte[] nblock = new byte[(block.length - boundary) / 2]; + + for (int i = 0; i < nblock.length; i++) + { + nblock[i] = block[2 * i + boundary + 1]; + } + + padBits = r - 1; + + return nblock; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/encodings/OAEPEncoding.java b/src/com/google/bitcoin/bouncycastle/crypto/encodings/OAEPEncoding.java new file mode 100644 index 000000000..3a6bc3cad --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/encodings/OAEPEncoding.java @@ -0,0 +1,348 @@ +package com.google.bitcoin.bouncycastle.crypto.encodings; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; +import com.google.bitcoin.bouncycastle.crypto.digests.SHA1Digest; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; + +import java.security.SecureRandom; + +/** + * Optimal Asymmetric Encryption Padding (OAEP) - see PKCS 1 V 2. + */ +public class OAEPEncoding + implements AsymmetricBlockCipher +{ + private byte[] defHash; + private Digest hash; + private Digest mgf1Hash; + + private AsymmetricBlockCipher engine; + private SecureRandom random; + private boolean forEncryption; + + public OAEPEncoding( + AsymmetricBlockCipher cipher) + { + this(cipher, new SHA1Digest(), null); + } + + public OAEPEncoding( + AsymmetricBlockCipher cipher, + Digest hash) + { + this(cipher, hash, null); + } + + public OAEPEncoding( + AsymmetricBlockCipher cipher, + Digest hash, + byte[] encodingParams) + { + this(cipher, hash, hash, encodingParams); + } + + public OAEPEncoding( + AsymmetricBlockCipher cipher, + Digest hash, + Digest mgf1Hash, + byte[] encodingParams) + { + this.engine = cipher; + this.hash = hash; + this.mgf1Hash = mgf1Hash; + this.defHash = new byte[hash.getDigestSize()]; + + if (encodingParams != null) + { + hash.update(encodingParams, 0, encodingParams.length); + } + + hash.doFinal(defHash, 0); + } + + public AsymmetricBlockCipher getUnderlyingCipher() + { + return engine; + } + + public void init( + boolean forEncryption, + CipherParameters param) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + } + else + { + this.random = new SecureRandom(); + } + + engine.init(forEncryption, param); + + this.forEncryption = forEncryption; + } + + public int getInputBlockSize() + { + int baseBlockSize = engine.getInputBlockSize(); + + if (forEncryption) + { + return baseBlockSize - 1 - 2 * defHash.length; + } + else + { + return baseBlockSize; + } + } + + public int getOutputBlockSize() + { + int baseBlockSize = engine.getOutputBlockSize(); + + if (forEncryption) + { + return baseBlockSize; + } + else + { + return baseBlockSize - 1 - 2 * defHash.length; + } + } + + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (forEncryption) + { + return encodeBlock(in, inOff, inLen); + } + else + { + return decodeBlock(in, inOff, inLen); + } + } + + public byte[] encodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + byte[] block = new byte[getInputBlockSize() + 1 + 2 * defHash.length]; + + // + // copy in the message + // + System.arraycopy(in, inOff, block, block.length - inLen, inLen); + + // + // add sentinel + // + block[block.length - inLen - 1] = 0x01; + + // + // as the block is already zeroed - there's no need to add PS (the >= 0 pad of 0) + // + + // + // add the hash of the encoding params. + // + System.arraycopy(defHash, 0, block, defHash.length, defHash.length); + + // + // generate the seed. + // + byte[] seed = new byte[defHash.length]; + + random.nextBytes(seed); + + // + // mask the message block. + // + byte[] mask = maskGeneratorFunction1(seed, 0, seed.length, block.length - defHash.length); + + for (int i = defHash.length; i != block.length; i++) + { + block[i] ^= mask[i - defHash.length]; + } + + // + // add in the seed + // + System.arraycopy(seed, 0, block, 0, defHash.length); + + // + // mask the seed. + // + mask = maskGeneratorFunction1( + block, defHash.length, block.length - defHash.length, defHash.length); + + for (int i = 0; i != defHash.length; i++) + { + block[i] ^= mask[i]; + } + + return engine.processBlock(block, 0, block.length); + } + + /** + * @exception InvalidCipherTextException if the decrypted block turns out to + * be badly formatted. + */ + public byte[] decodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + byte[] data = engine.processBlock(in, inOff, inLen); + byte[] block; + + // + // as we may have zeros in our leading bytes for the block we produced + // on encryption, we need to make sure our decrypted block comes back + // the same size. + // + if (data.length < engine.getOutputBlockSize()) + { + block = new byte[engine.getOutputBlockSize()]; + + System.arraycopy(data, 0, block, block.length - data.length, data.length); + } + else + { + block = data; + } + + if (block.length < (2 * defHash.length) + 1) + { + throw new InvalidCipherTextException("data too short"); + } + + // + // unmask the seed. + // + byte[] mask = maskGeneratorFunction1( + block, defHash.length, block.length - defHash.length, defHash.length); + + for (int i = 0; i != defHash.length; i++) + { + block[i] ^= mask[i]; + } + + // + // unmask the message block. + // + mask = maskGeneratorFunction1(block, 0, defHash.length, block.length - defHash.length); + + for (int i = defHash.length; i != block.length; i++) + { + block[i] ^= mask[i - defHash.length]; + } + + // + // check the hash of the encoding params. + // + for (int i = 0; i != defHash.length; i++) + { + if (defHash[i] != block[defHash.length + i]) + { + throw new InvalidCipherTextException("data hash wrong"); + } + } + + // + // find the data block + // + int start; + + for (start = 2 * defHash.length; start != block.length; start++) + { + if (block[start] != 0) + { + break; + } + } + + if (start >= (block.length - 1) || block[start] != 1) + { + throw new InvalidCipherTextException("data start wrong " + start); + } + + start++; + + // + // extract the data block + // + byte[] output = new byte[block.length - start]; + + System.arraycopy(block, start, output, 0, output.length); + + return output; + } + + /** + * int to octet string. + */ + private void ItoOSP( + int i, + byte[] sp) + { + sp[0] = (byte)(i >>> 24); + sp[1] = (byte)(i >>> 16); + sp[2] = (byte)(i >>> 8); + sp[3] = (byte)(i >>> 0); + } + + /** + * mask generator function, as described in PKCS1v2. + */ + private byte[] maskGeneratorFunction1( + byte[] Z, + int zOff, + int zLen, + int length) + { + byte[] mask = new byte[length]; + byte[] hashBuf = new byte[mgf1Hash.getDigestSize()]; + byte[] C = new byte[4]; + int counter = 0; + + hash.reset(); + + do + { + ItoOSP(counter, C); + + mgf1Hash.update(Z, zOff, zLen); + mgf1Hash.update(C, 0, C.length); + mgf1Hash.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * hashBuf.length, hashBuf.length); + } + while (++counter < (length / hashBuf.length)); + + if ((counter * hashBuf.length) < length) + { + ItoOSP(counter, C); + + mgf1Hash.update(Z, zOff, zLen); + mgf1Hash.update(C, 0, C.length); + mgf1Hash.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * hashBuf.length, mask.length - (counter * hashBuf.length)); + } + + return mask; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/encodings/PKCS1Encoding.java b/src/com/google/bitcoin/bouncycastle/crypto/encodings/PKCS1Encoding.java new file mode 100644 index 000000000..8035847b3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/encodings/PKCS1Encoding.java @@ -0,0 +1,247 @@ +package com.google.bitcoin.bouncycastle.crypto.encodings; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; +import com.google.bitcoin.bouncycastle.crypto.params.AsymmetricKeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; + +/** + * this does your basic PKCS 1 v1.5 padding - whether or not you should be using this + * depends on your application - see PKCS1 Version 2 for details. + */ +public class PKCS1Encoding + implements AsymmetricBlockCipher +{ + /** + * some providers fail to include the leading zero in PKCS1 encoded blocks. If you need to + * work with one of these set the system property org.bouncycastle.pkcs1.strict to false. + *

+ * The system property is checked during construction of the encoding object, it is set to + * true by default. + *

+ */ + public static final String STRICT_LENGTH_ENABLED_PROPERTY = "org.bouncycastle.pkcs1.strict"; + + private static final int HEADER_LENGTH = 10; + + private SecureRandom random; + private AsymmetricBlockCipher engine; + private boolean forEncryption; + private boolean forPrivateKey; + private boolean useStrictLength; + + /** + * Basic constructor. + * @param cipher + */ + public PKCS1Encoding( + AsymmetricBlockCipher cipher) + { + this.engine = cipher; + this.useStrictLength = useStrict(); + } + + // + // for J2ME compatibility + // + private boolean useStrict() + { + // required if security manager has been installed. + String strict = (String)AccessController.doPrivileged(new PrivilegedAction() + { + public Object run() + { + return System.getProperty(STRICT_LENGTH_ENABLED_PROPERTY); + } + }); + + return strict == null || strict.equals("true"); + } + + public AsymmetricBlockCipher getUnderlyingCipher() + { + return engine; + } + + public void init( + boolean forEncryption, + CipherParameters param) + { + AsymmetricKeyParameter kParam; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + kParam = (AsymmetricKeyParameter)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + kParam = (AsymmetricKeyParameter)param; + } + + engine.init(forEncryption, param); + + this.forPrivateKey = kParam.isPrivate(); + this.forEncryption = forEncryption; + } + + public int getInputBlockSize() + { + int baseBlockSize = engine.getInputBlockSize(); + + if (forEncryption) + { + return baseBlockSize - HEADER_LENGTH; + } + else + { + return baseBlockSize; + } + } + + public int getOutputBlockSize() + { + int baseBlockSize = engine.getOutputBlockSize(); + + if (forEncryption) + { + return baseBlockSize; + } + else + { + return baseBlockSize - HEADER_LENGTH; + } + } + + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (forEncryption) + { + return encodeBlock(in, inOff, inLen); + } + else + { + return decodeBlock(in, inOff, inLen); + } + } + + private byte[] encodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (inLen > getInputBlockSize()) + { + throw new IllegalArgumentException("input data too large"); + } + + byte[] block = new byte[engine.getInputBlockSize()]; + + if (forPrivateKey) + { + block[0] = 0x01; // type code 1 + + for (int i = 1; i != block.length - inLen - 1; i++) + { + block[i] = (byte)0xFF; + } + } + else + { + random.nextBytes(block); // random fill + + block[0] = 0x02; // type code 2 + + // + // a zero byte marks the end of the padding, so all + // the pad bytes must be non-zero. + // + for (int i = 1; i != block.length - inLen - 1; i++) + { + while (block[i] == 0) + { + block[i] = (byte)random.nextInt(); + } + } + } + + block[block.length - inLen - 1] = 0x00; // mark the end of the padding + System.arraycopy(in, inOff, block, block.length - inLen, inLen); + + return engine.processBlock(block, 0, block.length); + } + + /** + * @exception InvalidCipherTextException if the decrypted block is not in PKCS1 format. + */ + private byte[] decodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + byte[] block = engine.processBlock(in, inOff, inLen); + + if (block.length < getOutputBlockSize()) + { + throw new InvalidCipherTextException("block truncated"); + } + + byte type = block[0]; + + if (type != 1 && type != 2) + { + throw new InvalidCipherTextException("unknown block type"); + } + + if (useStrictLength && block.length != engine.getOutputBlockSize()) + { + throw new InvalidCipherTextException("block incorrect size"); + } + + // + // find and extract the message block. + // + int start; + + for (start = 1; start != block.length; start++) + { + byte pad = block[start]; + + if (pad == 0) + { + break; + } + if (type == 1 && pad != (byte)0xff) + { + throw new InvalidCipherTextException("block padding incorrect"); + } + } + + start++; // data should start at the next byte + + if (start > block.length || start < HEADER_LENGTH) + { + throw new InvalidCipherTextException("no data in block"); + } + + byte[] result = new byte[block.length - start]; + + System.arraycopy(block, start, result, 0, result.length); + + return result; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/AESEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/AESEngine.java new file mode 100644 index 000000000..44e8547b0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/AESEngine.java @@ -0,0 +1,547 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * an implementation of the AES (Rijndael), from FIPS-197. + *

+ * For further details see: http://csrc.nist.gov/encryption/aes/. + * + * This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at + * http://fp.gladman.plus.com/cryptography_technology/rijndael/ + * + * There are three levels of tradeoff of speed vs memory + * Because java has no preprocessor, they are written as three separate classes from which to choose + * + * The fastest uses 8Kbytes of static tables to precompute round calculations, 4 256 word tables for encryption + * and 4 for decryption. + * + * The middle performance version uses only one 256 word table for each, for a total of 2Kbytes, + * adding 12 rotate operations per round to compute the values contained in the other tables from + * the contents of the first. + * + * The slowest version uses no static tables at all and computes the values in each round. + *

+ * This file contains the middle performance version with 2Kbytes of static tables for round precomputation. + * + */ +public class AESEngine + implements BlockCipher +{ + // The S box + private static final byte[] S = { + (byte)99, (byte)124, (byte)119, (byte)123, (byte)242, (byte)107, (byte)111, (byte)197, + (byte)48, (byte)1, (byte)103, (byte)43, (byte)254, (byte)215, (byte)171, (byte)118, + (byte)202, (byte)130, (byte)201, (byte)125, (byte)250, (byte)89, (byte)71, (byte)240, + (byte)173, (byte)212, (byte)162, (byte)175, (byte)156, (byte)164, (byte)114, (byte)192, + (byte)183, (byte)253, (byte)147, (byte)38, (byte)54, (byte)63, (byte)247, (byte)204, + (byte)52, (byte)165, (byte)229, (byte)241, (byte)113, (byte)216, (byte)49, (byte)21, + (byte)4, (byte)199, (byte)35, (byte)195, (byte)24, (byte)150, (byte)5, (byte)154, + (byte)7, (byte)18, (byte)128, (byte)226, (byte)235, (byte)39, (byte)178, (byte)117, + (byte)9, (byte)131, (byte)44, (byte)26, (byte)27, (byte)110, (byte)90, (byte)160, + (byte)82, (byte)59, (byte)214, (byte)179, (byte)41, (byte)227, (byte)47, (byte)132, + (byte)83, (byte)209, (byte)0, (byte)237, (byte)32, (byte)252, (byte)177, (byte)91, + (byte)106, (byte)203, (byte)190, (byte)57, (byte)74, (byte)76, (byte)88, (byte)207, + (byte)208, (byte)239, (byte)170, (byte)251, (byte)67, (byte)77, (byte)51, (byte)133, + (byte)69, (byte)249, (byte)2, (byte)127, (byte)80, (byte)60, (byte)159, (byte)168, + (byte)81, (byte)163, (byte)64, (byte)143, (byte)146, (byte)157, (byte)56, (byte)245, + (byte)188, (byte)182, (byte)218, (byte)33, (byte)16, (byte)255, (byte)243, (byte)210, + (byte)205, (byte)12, (byte)19, (byte)236, (byte)95, (byte)151, (byte)68, (byte)23, + (byte)196, (byte)167, (byte)126, (byte)61, (byte)100, (byte)93, (byte)25, (byte)115, + (byte)96, (byte)129, (byte)79, (byte)220, (byte)34, (byte)42, (byte)144, (byte)136, + (byte)70, (byte)238, (byte)184, (byte)20, (byte)222, (byte)94, (byte)11, (byte)219, + (byte)224, (byte)50, (byte)58, (byte)10, (byte)73, (byte)6, (byte)36, (byte)92, + (byte)194, (byte)211, (byte)172, (byte)98, (byte)145, (byte)149, (byte)228, (byte)121, + (byte)231, (byte)200, (byte)55, (byte)109, (byte)141, (byte)213, (byte)78, (byte)169, + (byte)108, (byte)86, (byte)244, (byte)234, (byte)101, (byte)122, (byte)174, (byte)8, + (byte)186, (byte)120, (byte)37, (byte)46, (byte)28, (byte)166, (byte)180, (byte)198, + (byte)232, (byte)221, (byte)116, (byte)31, (byte)75, (byte)189, (byte)139, (byte)138, + (byte)112, (byte)62, (byte)181, (byte)102, (byte)72, (byte)3, (byte)246, (byte)14, + (byte)97, (byte)53, (byte)87, (byte)185, (byte)134, (byte)193, (byte)29, (byte)158, + (byte)225, (byte)248, (byte)152, (byte)17, (byte)105, (byte)217, (byte)142, (byte)148, + (byte)155, (byte)30, (byte)135, (byte)233, (byte)206, (byte)85, (byte)40, (byte)223, + (byte)140, (byte)161, (byte)137, (byte)13, (byte)191, (byte)230, (byte)66, (byte)104, + (byte)65, (byte)153, (byte)45, (byte)15, (byte)176, (byte)84, (byte)187, (byte)22, + }; + + // The inverse S-box + private static final byte[] Si = { + (byte)82, (byte)9, (byte)106, (byte)213, (byte)48, (byte)54, (byte)165, (byte)56, + (byte)191, (byte)64, (byte)163, (byte)158, (byte)129, (byte)243, (byte)215, (byte)251, + (byte)124, (byte)227, (byte)57, (byte)130, (byte)155, (byte)47, (byte)255, (byte)135, + (byte)52, (byte)142, (byte)67, (byte)68, (byte)196, (byte)222, (byte)233, (byte)203, + (byte)84, (byte)123, (byte)148, (byte)50, (byte)166, (byte)194, (byte)35, (byte)61, + (byte)238, (byte)76, (byte)149, (byte)11, (byte)66, (byte)250, (byte)195, (byte)78, + (byte)8, (byte)46, (byte)161, (byte)102, (byte)40, (byte)217, (byte)36, (byte)178, + (byte)118, (byte)91, (byte)162, (byte)73, (byte)109, (byte)139, (byte)209, (byte)37, + (byte)114, (byte)248, (byte)246, (byte)100, (byte)134, (byte)104, (byte)152, (byte)22, + (byte)212, (byte)164, (byte)92, (byte)204, (byte)93, (byte)101, (byte)182, (byte)146, + (byte)108, (byte)112, (byte)72, (byte)80, (byte)253, (byte)237, (byte)185, (byte)218, + (byte)94, (byte)21, (byte)70, (byte)87, (byte)167, (byte)141, (byte)157, (byte)132, + (byte)144, (byte)216, (byte)171, (byte)0, (byte)140, (byte)188, (byte)211, (byte)10, + (byte)247, (byte)228, (byte)88, (byte)5, (byte)184, (byte)179, (byte)69, (byte)6, + (byte)208, (byte)44, (byte)30, (byte)143, (byte)202, (byte)63, (byte)15, (byte)2, + (byte)193, (byte)175, (byte)189, (byte)3, (byte)1, (byte)19, (byte)138, (byte)107, + (byte)58, (byte)145, (byte)17, (byte)65, (byte)79, (byte)103, (byte)220, (byte)234, + (byte)151, (byte)242, (byte)207, (byte)206, (byte)240, (byte)180, (byte)230, (byte)115, + (byte)150, (byte)172, (byte)116, (byte)34, (byte)231, (byte)173, (byte)53, (byte)133, + (byte)226, (byte)249, (byte)55, (byte)232, (byte)28, (byte)117, (byte)223, (byte)110, + (byte)71, (byte)241, (byte)26, (byte)113, (byte)29, (byte)41, (byte)197, (byte)137, + (byte)111, (byte)183, (byte)98, (byte)14, (byte)170, (byte)24, (byte)190, (byte)27, + (byte)252, (byte)86, (byte)62, (byte)75, (byte)198, (byte)210, (byte)121, (byte)32, + (byte)154, (byte)219, (byte)192, (byte)254, (byte)120, (byte)205, (byte)90, (byte)244, + (byte)31, (byte)221, (byte)168, (byte)51, (byte)136, (byte)7, (byte)199, (byte)49, + (byte)177, (byte)18, (byte)16, (byte)89, (byte)39, (byte)128, (byte)236, (byte)95, + (byte)96, (byte)81, (byte)127, (byte)169, (byte)25, (byte)181, (byte)74, (byte)13, + (byte)45, (byte)229, (byte)122, (byte)159, (byte)147, (byte)201, (byte)156, (byte)239, + (byte)160, (byte)224, (byte)59, (byte)77, (byte)174, (byte)42, (byte)245, (byte)176, + (byte)200, (byte)235, (byte)187, (byte)60, (byte)131, (byte)83, (byte)153, (byte)97, + (byte)23, (byte)43, (byte)4, (byte)126, (byte)186, (byte)119, (byte)214, (byte)38, + (byte)225, (byte)105, (byte)20, (byte)99, (byte)85, (byte)33, (byte)12, (byte)125, + }; + + // vector used in calculating key schedule (powers of x in GF(256)) + private static final int[] rcon = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 }; + + // precomputation tables of calculations for rounds + private static final int[] T0 = + { + 0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff, + 0xbd6b6bd6, 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102, + 0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, + 0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa, + 0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, 0xecadad41, + 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453, + 0x967272e4, 0x5bc0c09b, 0xc2b7b775, 0x1cfdfde1, 0xae93933d, + 0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83, + 0x5c343468, 0xf4a5a551, 0x34e5e5d1, 0x08f1f1f9, 0x937171e2, + 0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795, + 0x65232346, 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a, + 0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df, + 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912, + 0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, 0xb26e6edc, + 0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, + 0xceb3b37d, 0x7b292952, 0x3ee3e3dd, 0x712f2f5e, 0x97848413, + 0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040, + 0x1ffcfce3, 0xc8b1b179, 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d, + 0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0, + 0x4acfcf85, 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed, + 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a, + 0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78, + 0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, 0xc0404080, + 0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1, + 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, 0x63212142, 0x30101020, + 0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18, + 0x35131326, 0x2fececc3, 0xe15f5fbe, 0xa2979735, 0xcc444488, + 0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a, + 0xac6464c8, 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0, + 0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54, + 0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b, + 0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, 0x76dbdbad, + 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992, + 0x0a06060c, 0x6c242448, 0xe45c5cb8, 0x5dc2c29f, 0x6ed3d3bd, + 0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3, + 0x8b7979f2, 0x32e7e7d5, 0x43c8c88b, 0x5937376e, 0xb76d6dda, + 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8, + 0xfa5656ac, 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4, + 0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a, + 0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697, + 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, 0xdd4b4b96, + 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c, + 0xc4b5b571, 0xaa6666cc, 0xd8484890, 0x05030306, 0x01f6f6f7, + 0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969, + 0x91868617, 0x58c1c199, 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9, + 0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9, + 0x898e8e07, 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715, + 0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5, + 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65, + 0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, 0xb0999929, + 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, + 0x3a16162c}; + +private static final int[] Tinv0 = + { + 0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a, 0xcb6bab3b, + 0xf1459d1f, 0xab58faac, 0x9303e34b, 0x55fa3020, 0xf66d76ad, + 0x9176cc88, 0x254c02f5, 0xfcd7e54f, 0xd7cb2ac5, 0x80443526, + 0x8fa362b5, 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d, + 0x02752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b, 0xe75f8f03, + 0x959c9215, 0xeb7a6dbf, 0xda595295, 0x2d83bed4, 0xd3217458, + 0x2969e049, 0x44c8c98e, 0x6a89c275, 0x78798ef4, 0x6b3e5899, + 0xdd71b927, 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d, + 0x184adf63, 0x82311ae5, 0x60335197, 0x457f5362, 0xe07764b1, + 0x84ae6bbb, 0x1ca081fe, 0x942b08f9, 0x58684870, 0x19fd458f, + 0x876cde94, 0xb7f87b52, 0x23d373ab, 0xe2024b72, 0x578f1fe3, + 0x2aab5566, 0x0728ebb2, 0x03c2b52f, 0x9a7bc586, 0xa50837d3, + 0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a, + 0x92b479a7, 0xf0f207f3, 0xa1e2694e, 0xcdf4da65, 0xd5be0506, + 0x1f6234d1, 0x8afea6c4, 0x9d532e34, 0xa055f3a2, 0x32e18a05, + 0x75ebf6a4, 0x39ec830b, 0xaaef6040, 0x069f715e, 0x51106ebd, + 0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d, 0xb58d5491, + 0x055dc471, 0x6fd40604, 0xff155060, 0x24fb9819, 0x97e9bdd6, + 0xcc434089, 0x779ed967, 0xbd42e8b0, 0x888b8907, 0x385b19e7, + 0xdbeec879, 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x00000000, + 0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c, 0xfbff0efd, + 0x5638850f, 0x1ed5ae3d, 0x27392d36, 0x64d90f0a, 0x21a65c68, + 0xd1545b9b, 0x3a2e3624, 0xb1670a0c, 0x0fe75793, 0xd296eeb4, + 0x9e919b1b, 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c, + 0x0aba93e2, 0xe52aa0c0, 0x43e0223c, 0x1d171b12, 0x0b0d090e, + 0xadc78bf2, 0xb9a8b62d, 0xc8a91e14, 0x8519f157, 0x4c0775af, + 0xbbdd99ee, 0xfd607fa3, 0x9f2601f7, 0xbcf5725c, 0xc53b6644, + 0x347efb5b, 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8, + 0xcadc31d7, 0x10856342, 0x40229713, 0x2011c684, 0x7d244a85, + 0xf83dbbd2, 0x1132f9ae, 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc, + 0xec52860d, 0xd0e3c177, 0x6c16b32b, 0x99b970a9, 0xfa489411, + 0x2264e947, 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, 0xef903322, + 0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498, 0xcf81f5a6, + 0x28de7aa5, 0x268eb7da, 0xa4bfad3f, 0xe49d3a2c, 0x0d927850, + 0x9bcc5f6a, 0x62467e54, 0xc2138df6, 0xe8b8d890, 0x5ef7392e, + 0xf5afc382, 0xbe805d9f, 0x7c93d069, 0xa92dd56f, 0xb31225cf, + 0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb, 0x097826cd, + 0xf418596e, 0x01b79aec, 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa, + 0x08cfbc21, 0xe6e815ef, 0xd99be7ba, 0xce366f4a, 0xd4099fea, + 0xd67cb029, 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235, + 0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733, 0x4a9804f1, + 0xf7daec41, 0x0e50cd7f, 0x2ff69117, 0x8dd64d76, 0x4db0ef43, + 0x544daacc, 0xdf0496e4, 0xe3b5d19e, 0x1b886a4c, 0xb81f2cc1, + 0x7f516546, 0x04ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb, + 0x5a1d67b3, 0x52d2db92, 0x335610e9, 0x1347d66d, 0x8c61d79a, + 0x7a0ca137, 0x8e14f859, 0x893c13eb, 0xee27a9ce, 0x35c961b7, + 0xede51ce1, 0x3cb1477a, 0x59dfd29c, 0x3f73f255, 0x79ce1418, + 0xbf37c773, 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478, + 0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2, 0x72c31d16, + 0x0c25e2bc, 0x8b493c28, 0x41950dff, 0x7101a839, 0xdeb30c08, + 0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48, + 0x4257b8d0}; + + private int shift( + int r, + int shift) + { + return (r >>> shift) | (r << -shift); + } + + /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ + + private static final int m1 = 0x80808080; + private static final int m2 = 0x7f7f7f7f; + private static final int m3 = 0x0000001b; + + private int FFmulX(int x) + { + return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3)); + } + + /* + The following defines provide alternative definitions of FFmulX that might + give improved performance if a fast 32-bit multiply is not available. + + private int FFmulX(int x) { int u = x & m1; u |= (u >> 1); return ((x & m2) << 1) ^ ((u >>> 3) | (u >>> 6)); } + private static final int m4 = 0x1b1b1b1b; + private int FFmulX(int x) { int u = x & m1; return ((x & m2) << 1) ^ ((u - (u >>> 7)) & m4); } + + */ + + private int inv_mcol(int x) + { + int f2 = FFmulX(x); + int f4 = FFmulX(f2); + int f8 = FFmulX(f4); + int f9 = x ^ f8; + + return f2 ^ f4 ^ f8 ^ shift(f2 ^ f9, 8) ^ shift(f4 ^ f9, 16) ^ shift(f9, 24); + } + + private int subWord(int x) + { + return (S[x&255]&255 | ((S[(x>>8)&255]&255)<<8) | ((S[(x>>16)&255]&255)<<16) | S[(x>>24)&255]<<24); + } + + /** + * Calculate the necessary round keys + * The number of calculations depends on key size and block size + * AES specified a fixed block size of 128 bits and key sizes 128/192/256 bits + * This code is written assuming those are the only possible values + */ + private int[][] generateWorkingKey( + byte[] key, + boolean forEncryption) + { + int KC = key.length / 4; // key length in words + int t; + + if (((KC != 4) && (KC != 6) && (KC != 8)) || ((KC * 4) != key.length)) + { + throw new IllegalArgumentException("Key length not 128/192/256 bits."); + } + + ROUNDS = KC + 6; // This is not always true for the generalized Rijndael that allows larger block sizes + int[][] W = new int[ROUNDS+1][4]; // 4 words in a block + + // + // copy the key into the round key array + // + + t = 0; + int i = 0; + while (i < key.length) + { + W[t >> 2][t & 3] = (key[i]&0xff) | ((key[i+1]&0xff) << 8) | ((key[i+2]&0xff) << 16) | (key[i+3] << 24); + i+=4; + t++; + } + + // + // while not enough round key material calculated + // calculate new values + // + int k = (ROUNDS + 1) << 2; + for (i = KC; (i < k); i++) + { + int temp = W[(i-1)>>2][(i-1)&3]; + if ((i % KC) == 0) + { + temp = subWord(shift(temp, 8)) ^ rcon[(i / KC)-1]; + } + else if ((KC > 6) && ((i % KC) == 4)) + { + temp = subWord(temp); + } + + W[i>>2][i&3] = W[(i - KC)>>2][(i-KC)&3] ^ temp; + } + + if (!forEncryption) + { + for (int j = 1; j < ROUNDS; j++) + { + for (i = 0; i < 4; i++) + { + W[j][i] = inv_mcol(W[j][i]); + } + } + } + + return W; + } + + private int ROUNDS; + private int[][] WorkingKey = null; + private int C0, C1, C2, C3; + private boolean forEncryption; + + private static final int BLOCK_SIZE = 16; + + /** + * default constructor - 128 bit block size. + */ + public AESEngine() + { + } + + /** + * initialise an AES cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + WorkingKey = generateWorkingKey(((KeyParameter)params).getKey(), forEncryption); + this.forEncryption = forEncryption; + return; + } + + throw new IllegalArgumentException("invalid parameter passed to AES init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "AES"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (WorkingKey == null) + { + throw new IllegalStateException("AES engine not initialised"); + } + + if ((inOff + (32 / 2)) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + (32 / 2)) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (forEncryption) + { + unpackBlock(in, inOff); + encryptBlock(WorkingKey); + packBlock(out, outOff); + } + else + { + unpackBlock(in, inOff); + decryptBlock(WorkingKey); + packBlock(out, outOff); + } + + return BLOCK_SIZE; + } + + public void reset() + { + } + + private void unpackBlock( + byte[] bytes, + int off) + { + int index = off; + + C0 = (bytes[index++] & 0xff); + C0 |= (bytes[index++] & 0xff) << 8; + C0 |= (bytes[index++] & 0xff) << 16; + C0 |= bytes[index++] << 24; + + C1 = (bytes[index++] & 0xff); + C1 |= (bytes[index++] & 0xff) << 8; + C1 |= (bytes[index++] & 0xff) << 16; + C1 |= bytes[index++] << 24; + + C2 = (bytes[index++] & 0xff); + C2 |= (bytes[index++] & 0xff) << 8; + C2 |= (bytes[index++] & 0xff) << 16; + C2 |= bytes[index++] << 24; + + C3 = (bytes[index++] & 0xff); + C3 |= (bytes[index++] & 0xff) << 8; + C3 |= (bytes[index++] & 0xff) << 16; + C3 |= bytes[index++] << 24; + } + + private void packBlock( + byte[] bytes, + int off) + { + int index = off; + + bytes[index++] = (byte)C0; + bytes[index++] = (byte)(C0 >> 8); + bytes[index++] = (byte)(C0 >> 16); + bytes[index++] = (byte)(C0 >> 24); + + bytes[index++] = (byte)C1; + bytes[index++] = (byte)(C1 >> 8); + bytes[index++] = (byte)(C1 >> 16); + bytes[index++] = (byte)(C1 >> 24); + + bytes[index++] = (byte)C2; + bytes[index++] = (byte)(C2 >> 8); + bytes[index++] = (byte)(C2 >> 16); + bytes[index++] = (byte)(C2 >> 24); + + bytes[index++] = (byte)C3; + bytes[index++] = (byte)(C3 >> 8); + bytes[index++] = (byte)(C3 >> 16); + bytes[index++] = (byte)(C3 >> 24); + } + + + private void encryptBlock(int[][] KW) + { + int r, r0, r1, r2, r3; + + C0 ^= KW[0][0]; + C1 ^= KW[0][1]; + C2 ^= KW[0][2]; + C3 ^= KW[0][3]; + + r = 1; + + while (r < ROUNDS - 1) + { + r0 = T0[C0&255] ^ shift(T0[(C1>>8)&255], 24) ^ shift(T0[(C2>>16)&255],16) ^ shift(T0[(C3>>24)&255],8) ^ KW[r][0]; + r1 = T0[C1&255] ^ shift(T0[(C2>>8)&255], 24) ^ shift(T0[(C3>>16)&255], 16) ^ shift(T0[(C0>>24)&255], 8) ^ KW[r][1]; + r2 = T0[C2&255] ^ shift(T0[(C3>>8)&255], 24) ^ shift(T0[(C0>>16)&255], 16) ^ shift(T0[(C1>>24)&255], 8) ^ KW[r][2]; + r3 = T0[C3&255] ^ shift(T0[(C0>>8)&255], 24) ^ shift(T0[(C1>>16)&255], 16) ^ shift(T0[(C2>>24)&255], 8) ^ KW[r++][3]; + C0 = T0[r0&255] ^ shift(T0[(r1>>8)&255], 24) ^ shift(T0[(r2>>16)&255], 16) ^ shift(T0[(r3>>24)&255], 8) ^ KW[r][0]; + C1 = T0[r1&255] ^ shift(T0[(r2>>8)&255], 24) ^ shift(T0[(r3>>16)&255], 16) ^ shift(T0[(r0>>24)&255], 8) ^ KW[r][1]; + C2 = T0[r2&255] ^ shift(T0[(r3>>8)&255], 24) ^ shift(T0[(r0>>16)&255], 16) ^ shift(T0[(r1>>24)&255], 8) ^ KW[r][2]; + C3 = T0[r3&255] ^ shift(T0[(r0>>8)&255], 24) ^ shift(T0[(r1>>16)&255], 16) ^ shift(T0[(r2>>24)&255], 8) ^ KW[r++][3]; + } + + r0 = T0[C0&255] ^ shift(T0[(C1>>8)&255], 24) ^ shift(T0[(C2>>16)&255], 16) ^ shift(T0[(C3>>24)&255], 8) ^ KW[r][0]; + r1 = T0[C1&255] ^ shift(T0[(C2>>8)&255], 24) ^ shift(T0[(C3>>16)&255], 16) ^ shift(T0[(C0>>24)&255], 8) ^ KW[r][1]; + r2 = T0[C2&255] ^ shift(T0[(C3>>8)&255], 24) ^ shift(T0[(C0>>16)&255], 16) ^ shift(T0[(C1>>24)&255], 8) ^ KW[r][2]; + r3 = T0[C3&255] ^ shift(T0[(C0>>8)&255], 24) ^ shift(T0[(C1>>16)&255], 16) ^ shift(T0[(C2>>24)&255], 8) ^ KW[r++][3]; + + // the final round's table is a simple function of S so we don't use a whole other four tables for it + + C0 = (S[r0&255]&255) ^ ((S[(r1>>8)&255]&255)<<8) ^ ((S[(r2>>16)&255]&255)<<16) ^ (S[(r3>>24)&255]<<24) ^ KW[r][0]; + C1 = (S[r1&255]&255) ^ ((S[(r2>>8)&255]&255)<<8) ^ ((S[(r3>>16)&255]&255)<<16) ^ (S[(r0>>24)&255]<<24) ^ KW[r][1]; + C2 = (S[r2&255]&255) ^ ((S[(r3>>8)&255]&255)<<8) ^ ((S[(r0>>16)&255]&255)<<16) ^ (S[(r1>>24)&255]<<24) ^ KW[r][2]; + C3 = (S[r3&255]&255) ^ ((S[(r0>>8)&255]&255)<<8) ^ ((S[(r1>>16)&255]&255)<<16) ^ (S[(r2>>24)&255]<<24) ^ KW[r][3]; + + } + + private void decryptBlock(int[][] KW) + { + int r, r0, r1, r2, r3; + + C0 ^= KW[ROUNDS][0]; + C1 ^= KW[ROUNDS][1]; + C2 ^= KW[ROUNDS][2]; + C3 ^= KW[ROUNDS][3]; + + r = ROUNDS-1; + + while (r>1) + { + r0 = Tinv0[C0&255] ^ shift(Tinv0[(C3>>8)&255], 24) ^ shift(Tinv0[(C2>>16)&255], 16) ^ shift(Tinv0[(C1>>24)&255], 8) ^ KW[r][0]; + r1 = Tinv0[C1&255] ^ shift(Tinv0[(C0>>8)&255], 24) ^ shift(Tinv0[(C3>>16)&255], 16) ^ shift(Tinv0[(C2>>24)&255], 8) ^ KW[r][1]; + r2 = Tinv0[C2&255] ^ shift(Tinv0[(C1>>8)&255], 24) ^ shift(Tinv0[(C0>>16)&255], 16) ^ shift(Tinv0[(C3>>24)&255], 8) ^ KW[r][2]; + r3 = Tinv0[C3&255] ^ shift(Tinv0[(C2>>8)&255], 24) ^ shift(Tinv0[(C1>>16)&255], 16) ^ shift(Tinv0[(C0>>24)&255], 8) ^ KW[r--][3]; + C0 = Tinv0[r0&255] ^ shift(Tinv0[(r3>>8)&255], 24) ^ shift(Tinv0[(r2>>16)&255], 16) ^ shift(Tinv0[(r1>>24)&255], 8) ^ KW[r][0]; + C1 = Tinv0[r1&255] ^ shift(Tinv0[(r0>>8)&255], 24) ^ shift(Tinv0[(r3>>16)&255], 16) ^ shift(Tinv0[(r2>>24)&255], 8) ^ KW[r][1]; + C2 = Tinv0[r2&255] ^ shift(Tinv0[(r1>>8)&255], 24) ^ shift(Tinv0[(r0>>16)&255], 16) ^ shift(Tinv0[(r3>>24)&255], 8) ^ KW[r][2]; + C3 = Tinv0[r3&255] ^ shift(Tinv0[(r2>>8)&255], 24) ^ shift(Tinv0[(r1>>16)&255], 16) ^ shift(Tinv0[(r0>>24)&255], 8) ^ KW[r--][3]; + } + + r0 = Tinv0[C0&255] ^ shift(Tinv0[(C3>>8)&255], 24) ^ shift(Tinv0[(C2>>16)&255], 16) ^ shift(Tinv0[(C1>>24)&255], 8) ^ KW[r][0]; + r1 = Tinv0[C1&255] ^ shift(Tinv0[(C0>>8)&255], 24) ^ shift(Tinv0[(C3>>16)&255], 16) ^ shift(Tinv0[(C2>>24)&255], 8) ^ KW[r][1]; + r2 = Tinv0[C2&255] ^ shift(Tinv0[(C1>>8)&255], 24) ^ shift(Tinv0[(C0>>16)&255], 16) ^ shift(Tinv0[(C3>>24)&255], 8) ^ KW[r][2]; + r3 = Tinv0[C3&255] ^ shift(Tinv0[(C2>>8)&255], 24) ^ shift(Tinv0[(C1>>16)&255], 16) ^ shift(Tinv0[(C0>>24)&255], 8) ^ KW[r][3]; + + // the final round's table is a simple function of Si so we don't use a whole other four tables for it + + C0 = (Si[r0&255]&255) ^ ((Si[(r3>>8)&255]&255)<<8) ^ ((Si[(r2>>16)&255]&255)<<16) ^ (Si[(r1>>24)&255]<<24) ^ KW[0][0]; + C1 = (Si[r1&255]&255) ^ ((Si[(r0>>8)&255]&255)<<8) ^ ((Si[(r3>>16)&255]&255)<<16) ^ (Si[(r2>>24)&255]<<24) ^ KW[0][1]; + C2 = (Si[r2&255]&255) ^ ((Si[(r1>>8)&255]&255)<<8) ^ ((Si[(r0>>16)&255]&255)<<16) ^ (Si[(r3>>24)&255]<<24) ^ KW[0][2]; + C3 = (Si[r3&255]&255) ^ ((Si[(r2>>8)&255]&255)<<8) ^ ((Si[(r1>>16)&255]&255)<<16) ^ (Si[(r0>>24)&255]<<24) ^ KW[0][3]; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/AESFastEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/AESFastEngine.java new file mode 100644 index 000000000..b48333368 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/AESFastEngine.java @@ -0,0 +1,876 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * an implementation of the AES (Rijndael), from FIPS-197. + *

+ * For further details see: http://csrc.nist.gov/encryption/aes/. + * + * This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at + * http://fp.gladman.plus.com/cryptography_technology/rijndael/ + * + * There are three levels of tradeoff of speed vs memory + * Because java has no preprocessor, they are written as three separate classes from which to choose + * + * The fastest uses 8Kbytes of static tables to precompute round calculations, 4 256 word tables for encryption + * and 4 for decryption. + * + * The middle performance version uses only one 256 word table for each, for a total of 2Kbytes, + * adding 12 rotate operations per round to compute the values contained in the other tables from + * the contents of the first + * + * The slowest version uses no static tables at all and computes the values in each round + *

+ * This file contains the fast version with 8Kbytes of static tables for round precomputation + * + */ +public class AESFastEngine + implements BlockCipher +{ + // The S box + private static final byte[] S = { + (byte)99, (byte)124, (byte)119, (byte)123, (byte)242, (byte)107, (byte)111, (byte)197, + (byte)48, (byte)1, (byte)103, (byte)43, (byte)254, (byte)215, (byte)171, (byte)118, + (byte)202, (byte)130, (byte)201, (byte)125, (byte)250, (byte)89, (byte)71, (byte)240, + (byte)173, (byte)212, (byte)162, (byte)175, (byte)156, (byte)164, (byte)114, (byte)192, + (byte)183, (byte)253, (byte)147, (byte)38, (byte)54, (byte)63, (byte)247, (byte)204, + (byte)52, (byte)165, (byte)229, (byte)241, (byte)113, (byte)216, (byte)49, (byte)21, + (byte)4, (byte)199, (byte)35, (byte)195, (byte)24, (byte)150, (byte)5, (byte)154, + (byte)7, (byte)18, (byte)128, (byte)226, (byte)235, (byte)39, (byte)178, (byte)117, + (byte)9, (byte)131, (byte)44, (byte)26, (byte)27, (byte)110, (byte)90, (byte)160, + (byte)82, (byte)59, (byte)214, (byte)179, (byte)41, (byte)227, (byte)47, (byte)132, + (byte)83, (byte)209, (byte)0, (byte)237, (byte)32, (byte)252, (byte)177, (byte)91, + (byte)106, (byte)203, (byte)190, (byte)57, (byte)74, (byte)76, (byte)88, (byte)207, + (byte)208, (byte)239, (byte)170, (byte)251, (byte)67, (byte)77, (byte)51, (byte)133, + (byte)69, (byte)249, (byte)2, (byte)127, (byte)80, (byte)60, (byte)159, (byte)168, + (byte)81, (byte)163, (byte)64, (byte)143, (byte)146, (byte)157, (byte)56, (byte)245, + (byte)188, (byte)182, (byte)218, (byte)33, (byte)16, (byte)255, (byte)243, (byte)210, + (byte)205, (byte)12, (byte)19, (byte)236, (byte)95, (byte)151, (byte)68, (byte)23, + (byte)196, (byte)167, (byte)126, (byte)61, (byte)100, (byte)93, (byte)25, (byte)115, + (byte)96, (byte)129, (byte)79, (byte)220, (byte)34, (byte)42, (byte)144, (byte)136, + (byte)70, (byte)238, (byte)184, (byte)20, (byte)222, (byte)94, (byte)11, (byte)219, + (byte)224, (byte)50, (byte)58, (byte)10, (byte)73, (byte)6, (byte)36, (byte)92, + (byte)194, (byte)211, (byte)172, (byte)98, (byte)145, (byte)149, (byte)228, (byte)121, + (byte)231, (byte)200, (byte)55, (byte)109, (byte)141, (byte)213, (byte)78, (byte)169, + (byte)108, (byte)86, (byte)244, (byte)234, (byte)101, (byte)122, (byte)174, (byte)8, + (byte)186, (byte)120, (byte)37, (byte)46, (byte)28, (byte)166, (byte)180, (byte)198, + (byte)232, (byte)221, (byte)116, (byte)31, (byte)75, (byte)189, (byte)139, (byte)138, + (byte)112, (byte)62, (byte)181, (byte)102, (byte)72, (byte)3, (byte)246, (byte)14, + (byte)97, (byte)53, (byte)87, (byte)185, (byte)134, (byte)193, (byte)29, (byte)158, + (byte)225, (byte)248, (byte)152, (byte)17, (byte)105, (byte)217, (byte)142, (byte)148, + (byte)155, (byte)30, (byte)135, (byte)233, (byte)206, (byte)85, (byte)40, (byte)223, + (byte)140, (byte)161, (byte)137, (byte)13, (byte)191, (byte)230, (byte)66, (byte)104, + (byte)65, (byte)153, (byte)45, (byte)15, (byte)176, (byte)84, (byte)187, (byte)22, + }; + + // The inverse S-box + private static final byte[] Si = { + (byte)82, (byte)9, (byte)106, (byte)213, (byte)48, (byte)54, (byte)165, (byte)56, + (byte)191, (byte)64, (byte)163, (byte)158, (byte)129, (byte)243, (byte)215, (byte)251, + (byte)124, (byte)227, (byte)57, (byte)130, (byte)155, (byte)47, (byte)255, (byte)135, + (byte)52, (byte)142, (byte)67, (byte)68, (byte)196, (byte)222, (byte)233, (byte)203, + (byte)84, (byte)123, (byte)148, (byte)50, (byte)166, (byte)194, (byte)35, (byte)61, + (byte)238, (byte)76, (byte)149, (byte)11, (byte)66, (byte)250, (byte)195, (byte)78, + (byte)8, (byte)46, (byte)161, (byte)102, (byte)40, (byte)217, (byte)36, (byte)178, + (byte)118, (byte)91, (byte)162, (byte)73, (byte)109, (byte)139, (byte)209, (byte)37, + (byte)114, (byte)248, (byte)246, (byte)100, (byte)134, (byte)104, (byte)152, (byte)22, + (byte)212, (byte)164, (byte)92, (byte)204, (byte)93, (byte)101, (byte)182, (byte)146, + (byte)108, (byte)112, (byte)72, (byte)80, (byte)253, (byte)237, (byte)185, (byte)218, + (byte)94, (byte)21, (byte)70, (byte)87, (byte)167, (byte)141, (byte)157, (byte)132, + (byte)144, (byte)216, (byte)171, (byte)0, (byte)140, (byte)188, (byte)211, (byte)10, + (byte)247, (byte)228, (byte)88, (byte)5, (byte)184, (byte)179, (byte)69, (byte)6, + (byte)208, (byte)44, (byte)30, (byte)143, (byte)202, (byte)63, (byte)15, (byte)2, + (byte)193, (byte)175, (byte)189, (byte)3, (byte)1, (byte)19, (byte)138, (byte)107, + (byte)58, (byte)145, (byte)17, (byte)65, (byte)79, (byte)103, (byte)220, (byte)234, + (byte)151, (byte)242, (byte)207, (byte)206, (byte)240, (byte)180, (byte)230, (byte)115, + (byte)150, (byte)172, (byte)116, (byte)34, (byte)231, (byte)173, (byte)53, (byte)133, + (byte)226, (byte)249, (byte)55, (byte)232, (byte)28, (byte)117, (byte)223, (byte)110, + (byte)71, (byte)241, (byte)26, (byte)113, (byte)29, (byte)41, (byte)197, (byte)137, + (byte)111, (byte)183, (byte)98, (byte)14, (byte)170, (byte)24, (byte)190, (byte)27, + (byte)252, (byte)86, (byte)62, (byte)75, (byte)198, (byte)210, (byte)121, (byte)32, + (byte)154, (byte)219, (byte)192, (byte)254, (byte)120, (byte)205, (byte)90, (byte)244, + (byte)31, (byte)221, (byte)168, (byte)51, (byte)136, (byte)7, (byte)199, (byte)49, + (byte)177, (byte)18, (byte)16, (byte)89, (byte)39, (byte)128, (byte)236, (byte)95, + (byte)96, (byte)81, (byte)127, (byte)169, (byte)25, (byte)181, (byte)74, (byte)13, + (byte)45, (byte)229, (byte)122, (byte)159, (byte)147, (byte)201, (byte)156, (byte)239, + (byte)160, (byte)224, (byte)59, (byte)77, (byte)174, (byte)42, (byte)245, (byte)176, + (byte)200, (byte)235, (byte)187, (byte)60, (byte)131, (byte)83, (byte)153, (byte)97, + (byte)23, (byte)43, (byte)4, (byte)126, (byte)186, (byte)119, (byte)214, (byte)38, + (byte)225, (byte)105, (byte)20, (byte)99, (byte)85, (byte)33, (byte)12, (byte)125, + }; + + // vector used in calculating key schedule (powers of x in GF(256)) + private static final int[] rcon = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 }; + + // precomputation tables of calculations for rounds + private static final int[] T0 = + { + 0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff, + 0xbd6b6bd6, 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102, + 0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, + 0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa, + 0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, 0xecadad41, + 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453, + 0x967272e4, 0x5bc0c09b, 0xc2b7b775, 0x1cfdfde1, 0xae93933d, + 0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83, + 0x5c343468, 0xf4a5a551, 0x34e5e5d1, 0x08f1f1f9, 0x937171e2, + 0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795, + 0x65232346, 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a, + 0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df, + 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912, + 0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, 0xb26e6edc, + 0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, + 0xceb3b37d, 0x7b292952, 0x3ee3e3dd, 0x712f2f5e, 0x97848413, + 0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040, + 0x1ffcfce3, 0xc8b1b179, 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d, + 0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0, + 0x4acfcf85, 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed, + 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a, + 0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78, + 0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, 0xc0404080, + 0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1, + 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, 0x63212142, 0x30101020, + 0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18, + 0x35131326, 0x2fececc3, 0xe15f5fbe, 0xa2979735, 0xcc444488, + 0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a, + 0xac6464c8, 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0, + 0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54, + 0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b, + 0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, 0x76dbdbad, + 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992, + 0x0a06060c, 0x6c242448, 0xe45c5cb8, 0x5dc2c29f, 0x6ed3d3bd, + 0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3, + 0x8b7979f2, 0x32e7e7d5, 0x43c8c88b, 0x5937376e, 0xb76d6dda, + 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8, + 0xfa5656ac, 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4, + 0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a, + 0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697, + 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, 0xdd4b4b96, + 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c, + 0xc4b5b571, 0xaa6666cc, 0xd8484890, 0x05030306, 0x01f6f6f7, + 0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969, + 0x91868617, 0x58c1c199, 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9, + 0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9, + 0x898e8e07, 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715, + 0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5, + 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65, + 0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, 0xb0999929, + 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, + 0x3a16162c}; + + private static final int[] T1 = + { + 0x6363c6a5, 0x7c7cf884, 0x7777ee99, 0x7b7bf68d, 0xf2f2ff0d, + 0x6b6bd6bd, 0x6f6fdeb1, 0xc5c59154, 0x30306050, 0x01010203, + 0x6767cea9, 0x2b2b567d, 0xfefee719, 0xd7d7b562, 0xabab4de6, + 0x7676ec9a, 0xcaca8f45, 0x82821f9d, 0xc9c98940, 0x7d7dfa87, + 0xfafaef15, 0x5959b2eb, 0x47478ec9, 0xf0f0fb0b, 0xadad41ec, + 0xd4d4b367, 0xa2a25ffd, 0xafaf45ea, 0x9c9c23bf, 0xa4a453f7, + 0x7272e496, 0xc0c09b5b, 0xb7b775c2, 0xfdfde11c, 0x93933dae, + 0x26264c6a, 0x36366c5a, 0x3f3f7e41, 0xf7f7f502, 0xcccc834f, + 0x3434685c, 0xa5a551f4, 0xe5e5d134, 0xf1f1f908, 0x7171e293, + 0xd8d8ab73, 0x31316253, 0x15152a3f, 0x0404080c, 0xc7c79552, + 0x23234665, 0xc3c39d5e, 0x18183028, 0x969637a1, 0x05050a0f, + 0x9a9a2fb5, 0x07070e09, 0x12122436, 0x80801b9b, 0xe2e2df3d, + 0xebebcd26, 0x27274e69, 0xb2b27fcd, 0x7575ea9f, 0x0909121b, + 0x83831d9e, 0x2c2c5874, 0x1a1a342e, 0x1b1b362d, 0x6e6edcb2, + 0x5a5ab4ee, 0xa0a05bfb, 0x5252a4f6, 0x3b3b764d, 0xd6d6b761, + 0xb3b37dce, 0x2929527b, 0xe3e3dd3e, 0x2f2f5e71, 0x84841397, + 0x5353a6f5, 0xd1d1b968, 0x00000000, 0xededc12c, 0x20204060, + 0xfcfce31f, 0xb1b179c8, 0x5b5bb6ed, 0x6a6ad4be, 0xcbcb8d46, + 0xbebe67d9, 0x3939724b, 0x4a4a94de, 0x4c4c98d4, 0x5858b0e8, + 0xcfcf854a, 0xd0d0bb6b, 0xefefc52a, 0xaaaa4fe5, 0xfbfbed16, + 0x434386c5, 0x4d4d9ad7, 0x33336655, 0x85851194, 0x45458acf, + 0xf9f9e910, 0x02020406, 0x7f7ffe81, 0x5050a0f0, 0x3c3c7844, + 0x9f9f25ba, 0xa8a84be3, 0x5151a2f3, 0xa3a35dfe, 0x404080c0, + 0x8f8f058a, 0x92923fad, 0x9d9d21bc, 0x38387048, 0xf5f5f104, + 0xbcbc63df, 0xb6b677c1, 0xdadaaf75, 0x21214263, 0x10102030, + 0xffffe51a, 0xf3f3fd0e, 0xd2d2bf6d, 0xcdcd814c, 0x0c0c1814, + 0x13132635, 0xececc32f, 0x5f5fbee1, 0x979735a2, 0x444488cc, + 0x17172e39, 0xc4c49357, 0xa7a755f2, 0x7e7efc82, 0x3d3d7a47, + 0x6464c8ac, 0x5d5dbae7, 0x1919322b, 0x7373e695, 0x6060c0a0, + 0x81811998, 0x4f4f9ed1, 0xdcdca37f, 0x22224466, 0x2a2a547e, + 0x90903bab, 0x88880b83, 0x46468cca, 0xeeeec729, 0xb8b86bd3, + 0x1414283c, 0xdedea779, 0x5e5ebce2, 0x0b0b161d, 0xdbdbad76, + 0xe0e0db3b, 0x32326456, 0x3a3a744e, 0x0a0a141e, 0x494992db, + 0x06060c0a, 0x2424486c, 0x5c5cb8e4, 0xc2c29f5d, 0xd3d3bd6e, + 0xacac43ef, 0x6262c4a6, 0x919139a8, 0x959531a4, 0xe4e4d337, + 0x7979f28b, 0xe7e7d532, 0xc8c88b43, 0x37376e59, 0x6d6ddab7, + 0x8d8d018c, 0xd5d5b164, 0x4e4e9cd2, 0xa9a949e0, 0x6c6cd8b4, + 0x5656acfa, 0xf4f4f307, 0xeaeacf25, 0x6565caaf, 0x7a7af48e, + 0xaeae47e9, 0x08081018, 0xbaba6fd5, 0x7878f088, 0x25254a6f, + 0x2e2e5c72, 0x1c1c3824, 0xa6a657f1, 0xb4b473c7, 0xc6c69751, + 0xe8e8cb23, 0xdddda17c, 0x7474e89c, 0x1f1f3e21, 0x4b4b96dd, + 0xbdbd61dc, 0x8b8b0d86, 0x8a8a0f85, 0x7070e090, 0x3e3e7c42, + 0xb5b571c4, 0x6666ccaa, 0x484890d8, 0x03030605, 0xf6f6f701, + 0x0e0e1c12, 0x6161c2a3, 0x35356a5f, 0x5757aef9, 0xb9b969d0, + 0x86861791, 0xc1c19958, 0x1d1d3a27, 0x9e9e27b9, 0xe1e1d938, + 0xf8f8eb13, 0x98982bb3, 0x11112233, 0x6969d2bb, 0xd9d9a970, + 0x8e8e0789, 0x949433a7, 0x9b9b2db6, 0x1e1e3c22, 0x87871592, + 0xe9e9c920, 0xcece8749, 0x5555aaff, 0x28285078, 0xdfdfa57a, + 0x8c8c038f, 0xa1a159f8, 0x89890980, 0x0d0d1a17, 0xbfbf65da, + 0xe6e6d731, 0x424284c6, 0x6868d0b8, 0x414182c3, 0x999929b0, + 0x2d2d5a77, 0x0f0f1e11, 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6, + 0x16162c3a}; + + private static final int[] T2 = + { + 0x63c6a563, 0x7cf8847c, 0x77ee9977, 0x7bf68d7b, 0xf2ff0df2, + 0x6bd6bd6b, 0x6fdeb16f, 0xc59154c5, 0x30605030, 0x01020301, + 0x67cea967, 0x2b567d2b, 0xfee719fe, 0xd7b562d7, 0xab4de6ab, + 0x76ec9a76, 0xca8f45ca, 0x821f9d82, 0xc98940c9, 0x7dfa877d, + 0xfaef15fa, 0x59b2eb59, 0x478ec947, 0xf0fb0bf0, 0xad41ecad, + 0xd4b367d4, 0xa25ffda2, 0xaf45eaaf, 0x9c23bf9c, 0xa453f7a4, + 0x72e49672, 0xc09b5bc0, 0xb775c2b7, 0xfde11cfd, 0x933dae93, + 0x264c6a26, 0x366c5a36, 0x3f7e413f, 0xf7f502f7, 0xcc834fcc, + 0x34685c34, 0xa551f4a5, 0xe5d134e5, 0xf1f908f1, 0x71e29371, + 0xd8ab73d8, 0x31625331, 0x152a3f15, 0x04080c04, 0xc79552c7, + 0x23466523, 0xc39d5ec3, 0x18302818, 0x9637a196, 0x050a0f05, + 0x9a2fb59a, 0x070e0907, 0x12243612, 0x801b9b80, 0xe2df3de2, + 0xebcd26eb, 0x274e6927, 0xb27fcdb2, 0x75ea9f75, 0x09121b09, + 0x831d9e83, 0x2c58742c, 0x1a342e1a, 0x1b362d1b, 0x6edcb26e, + 0x5ab4ee5a, 0xa05bfba0, 0x52a4f652, 0x3b764d3b, 0xd6b761d6, + 0xb37dceb3, 0x29527b29, 0xe3dd3ee3, 0x2f5e712f, 0x84139784, + 0x53a6f553, 0xd1b968d1, 0x00000000, 0xedc12ced, 0x20406020, + 0xfce31ffc, 0xb179c8b1, 0x5bb6ed5b, 0x6ad4be6a, 0xcb8d46cb, + 0xbe67d9be, 0x39724b39, 0x4a94de4a, 0x4c98d44c, 0x58b0e858, + 0xcf854acf, 0xd0bb6bd0, 0xefc52aef, 0xaa4fe5aa, 0xfbed16fb, + 0x4386c543, 0x4d9ad74d, 0x33665533, 0x85119485, 0x458acf45, + 0xf9e910f9, 0x02040602, 0x7ffe817f, 0x50a0f050, 0x3c78443c, + 0x9f25ba9f, 0xa84be3a8, 0x51a2f351, 0xa35dfea3, 0x4080c040, + 0x8f058a8f, 0x923fad92, 0x9d21bc9d, 0x38704838, 0xf5f104f5, + 0xbc63dfbc, 0xb677c1b6, 0xdaaf75da, 0x21426321, 0x10203010, + 0xffe51aff, 0xf3fd0ef3, 0xd2bf6dd2, 0xcd814ccd, 0x0c18140c, + 0x13263513, 0xecc32fec, 0x5fbee15f, 0x9735a297, 0x4488cc44, + 0x172e3917, 0xc49357c4, 0xa755f2a7, 0x7efc827e, 0x3d7a473d, + 0x64c8ac64, 0x5dbae75d, 0x19322b19, 0x73e69573, 0x60c0a060, + 0x81199881, 0x4f9ed14f, 0xdca37fdc, 0x22446622, 0x2a547e2a, + 0x903bab90, 0x880b8388, 0x468cca46, 0xeec729ee, 0xb86bd3b8, + 0x14283c14, 0xdea779de, 0x5ebce25e, 0x0b161d0b, 0xdbad76db, + 0xe0db3be0, 0x32645632, 0x3a744e3a, 0x0a141e0a, 0x4992db49, + 0x060c0a06, 0x24486c24, 0x5cb8e45c, 0xc29f5dc2, 0xd3bd6ed3, + 0xac43efac, 0x62c4a662, 0x9139a891, 0x9531a495, 0xe4d337e4, + 0x79f28b79, 0xe7d532e7, 0xc88b43c8, 0x376e5937, 0x6ddab76d, + 0x8d018c8d, 0xd5b164d5, 0x4e9cd24e, 0xa949e0a9, 0x6cd8b46c, + 0x56acfa56, 0xf4f307f4, 0xeacf25ea, 0x65caaf65, 0x7af48e7a, + 0xae47e9ae, 0x08101808, 0xba6fd5ba, 0x78f08878, 0x254a6f25, + 0x2e5c722e, 0x1c38241c, 0xa657f1a6, 0xb473c7b4, 0xc69751c6, + 0xe8cb23e8, 0xdda17cdd, 0x74e89c74, 0x1f3e211f, 0x4b96dd4b, + 0xbd61dcbd, 0x8b0d868b, 0x8a0f858a, 0x70e09070, 0x3e7c423e, + 0xb571c4b5, 0x66ccaa66, 0x4890d848, 0x03060503, 0xf6f701f6, + 0x0e1c120e, 0x61c2a361, 0x356a5f35, 0x57aef957, 0xb969d0b9, + 0x86179186, 0xc19958c1, 0x1d3a271d, 0x9e27b99e, 0xe1d938e1, + 0xf8eb13f8, 0x982bb398, 0x11223311, 0x69d2bb69, 0xd9a970d9, + 0x8e07898e, 0x9433a794, 0x9b2db69b, 0x1e3c221e, 0x87159287, + 0xe9c920e9, 0xce8749ce, 0x55aaff55, 0x28507828, 0xdfa57adf, + 0x8c038f8c, 0xa159f8a1, 0x89098089, 0x0d1a170d, 0xbf65dabf, + 0xe6d731e6, 0x4284c642, 0x68d0b868, 0x4182c341, 0x9929b099, + 0x2d5a772d, 0x0f1e110f, 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb, + 0x162c3a16}; + + private static final int[] T3 = + { + 0xc6a56363, 0xf8847c7c, 0xee997777, 0xf68d7b7b, 0xff0df2f2, + 0xd6bd6b6b, 0xdeb16f6f, 0x9154c5c5, 0x60503030, 0x02030101, + 0xcea96767, 0x567d2b2b, 0xe719fefe, 0xb562d7d7, 0x4de6abab, + 0xec9a7676, 0x8f45caca, 0x1f9d8282, 0x8940c9c9, 0xfa877d7d, + 0xef15fafa, 0xb2eb5959, 0x8ec94747, 0xfb0bf0f0, 0x41ecadad, + 0xb367d4d4, 0x5ffda2a2, 0x45eaafaf, 0x23bf9c9c, 0x53f7a4a4, + 0xe4967272, 0x9b5bc0c0, 0x75c2b7b7, 0xe11cfdfd, 0x3dae9393, + 0x4c6a2626, 0x6c5a3636, 0x7e413f3f, 0xf502f7f7, 0x834fcccc, + 0x685c3434, 0x51f4a5a5, 0xd134e5e5, 0xf908f1f1, 0xe2937171, + 0xab73d8d8, 0x62533131, 0x2a3f1515, 0x080c0404, 0x9552c7c7, + 0x46652323, 0x9d5ec3c3, 0x30281818, 0x37a19696, 0x0a0f0505, + 0x2fb59a9a, 0x0e090707, 0x24361212, 0x1b9b8080, 0xdf3de2e2, + 0xcd26ebeb, 0x4e692727, 0x7fcdb2b2, 0xea9f7575, 0x121b0909, + 0x1d9e8383, 0x58742c2c, 0x342e1a1a, 0x362d1b1b, 0xdcb26e6e, + 0xb4ee5a5a, 0x5bfba0a0, 0xa4f65252, 0x764d3b3b, 0xb761d6d6, + 0x7dceb3b3, 0x527b2929, 0xdd3ee3e3, 0x5e712f2f, 0x13978484, + 0xa6f55353, 0xb968d1d1, 0x00000000, 0xc12ceded, 0x40602020, + 0xe31ffcfc, 0x79c8b1b1, 0xb6ed5b5b, 0xd4be6a6a, 0x8d46cbcb, + 0x67d9bebe, 0x724b3939, 0x94de4a4a, 0x98d44c4c, 0xb0e85858, + 0x854acfcf, 0xbb6bd0d0, 0xc52aefef, 0x4fe5aaaa, 0xed16fbfb, + 0x86c54343, 0x9ad74d4d, 0x66553333, 0x11948585, 0x8acf4545, + 0xe910f9f9, 0x04060202, 0xfe817f7f, 0xa0f05050, 0x78443c3c, + 0x25ba9f9f, 0x4be3a8a8, 0xa2f35151, 0x5dfea3a3, 0x80c04040, + 0x058a8f8f, 0x3fad9292, 0x21bc9d9d, 0x70483838, 0xf104f5f5, + 0x63dfbcbc, 0x77c1b6b6, 0xaf75dada, 0x42632121, 0x20301010, + 0xe51affff, 0xfd0ef3f3, 0xbf6dd2d2, 0x814ccdcd, 0x18140c0c, + 0x26351313, 0xc32fecec, 0xbee15f5f, 0x35a29797, 0x88cc4444, + 0x2e391717, 0x9357c4c4, 0x55f2a7a7, 0xfc827e7e, 0x7a473d3d, + 0xc8ac6464, 0xbae75d5d, 0x322b1919, 0xe6957373, 0xc0a06060, + 0x19988181, 0x9ed14f4f, 0xa37fdcdc, 0x44662222, 0x547e2a2a, + 0x3bab9090, 0x0b838888, 0x8cca4646, 0xc729eeee, 0x6bd3b8b8, + 0x283c1414, 0xa779dede, 0xbce25e5e, 0x161d0b0b, 0xad76dbdb, + 0xdb3be0e0, 0x64563232, 0x744e3a3a, 0x141e0a0a, 0x92db4949, + 0x0c0a0606, 0x486c2424, 0xb8e45c5c, 0x9f5dc2c2, 0xbd6ed3d3, + 0x43efacac, 0xc4a66262, 0x39a89191, 0x31a49595, 0xd337e4e4, + 0xf28b7979, 0xd532e7e7, 0x8b43c8c8, 0x6e593737, 0xdab76d6d, + 0x018c8d8d, 0xb164d5d5, 0x9cd24e4e, 0x49e0a9a9, 0xd8b46c6c, + 0xacfa5656, 0xf307f4f4, 0xcf25eaea, 0xcaaf6565, 0xf48e7a7a, + 0x47e9aeae, 0x10180808, 0x6fd5baba, 0xf0887878, 0x4a6f2525, + 0x5c722e2e, 0x38241c1c, 0x57f1a6a6, 0x73c7b4b4, 0x9751c6c6, + 0xcb23e8e8, 0xa17cdddd, 0xe89c7474, 0x3e211f1f, 0x96dd4b4b, + 0x61dcbdbd, 0x0d868b8b, 0x0f858a8a, 0xe0907070, 0x7c423e3e, + 0x71c4b5b5, 0xccaa6666, 0x90d84848, 0x06050303, 0xf701f6f6, + 0x1c120e0e, 0xc2a36161, 0x6a5f3535, 0xaef95757, 0x69d0b9b9, + 0x17918686, 0x9958c1c1, 0x3a271d1d, 0x27b99e9e, 0xd938e1e1, + 0xeb13f8f8, 0x2bb39898, 0x22331111, 0xd2bb6969, 0xa970d9d9, + 0x07898e8e, 0x33a79494, 0x2db69b9b, 0x3c221e1e, 0x15928787, + 0xc920e9e9, 0x8749cece, 0xaaff5555, 0x50782828, 0xa57adfdf, + 0x038f8c8c, 0x59f8a1a1, 0x09808989, 0x1a170d0d, 0x65dabfbf, + 0xd731e6e6, 0x84c64242, 0xd0b86868, 0x82c34141, 0x29b09999, + 0x5a772d2d, 0x1e110f0f, 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb, + 0x2c3a1616}; + + private static final int[] Tinv0 = + { + 0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a, 0xcb6bab3b, + 0xf1459d1f, 0xab58faac, 0x9303e34b, 0x55fa3020, 0xf66d76ad, + 0x9176cc88, 0x254c02f5, 0xfcd7e54f, 0xd7cb2ac5, 0x80443526, + 0x8fa362b5, 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d, + 0x02752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b, 0xe75f8f03, + 0x959c9215, 0xeb7a6dbf, 0xda595295, 0x2d83bed4, 0xd3217458, + 0x2969e049, 0x44c8c98e, 0x6a89c275, 0x78798ef4, 0x6b3e5899, + 0xdd71b927, 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d, + 0x184adf63, 0x82311ae5, 0x60335197, 0x457f5362, 0xe07764b1, + 0x84ae6bbb, 0x1ca081fe, 0x942b08f9, 0x58684870, 0x19fd458f, + 0x876cde94, 0xb7f87b52, 0x23d373ab, 0xe2024b72, 0x578f1fe3, + 0x2aab5566, 0x0728ebb2, 0x03c2b52f, 0x9a7bc586, 0xa50837d3, + 0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a, + 0x92b479a7, 0xf0f207f3, 0xa1e2694e, 0xcdf4da65, 0xd5be0506, + 0x1f6234d1, 0x8afea6c4, 0x9d532e34, 0xa055f3a2, 0x32e18a05, + 0x75ebf6a4, 0x39ec830b, 0xaaef6040, 0x069f715e, 0x51106ebd, + 0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d, 0xb58d5491, + 0x055dc471, 0x6fd40604, 0xff155060, 0x24fb9819, 0x97e9bdd6, + 0xcc434089, 0x779ed967, 0xbd42e8b0, 0x888b8907, 0x385b19e7, + 0xdbeec879, 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x00000000, + 0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c, 0xfbff0efd, + 0x5638850f, 0x1ed5ae3d, 0x27392d36, 0x64d90f0a, 0x21a65c68, + 0xd1545b9b, 0x3a2e3624, 0xb1670a0c, 0x0fe75793, 0xd296eeb4, + 0x9e919b1b, 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c, + 0x0aba93e2, 0xe52aa0c0, 0x43e0223c, 0x1d171b12, 0x0b0d090e, + 0xadc78bf2, 0xb9a8b62d, 0xc8a91e14, 0x8519f157, 0x4c0775af, + 0xbbdd99ee, 0xfd607fa3, 0x9f2601f7, 0xbcf5725c, 0xc53b6644, + 0x347efb5b, 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8, + 0xcadc31d7, 0x10856342, 0x40229713, 0x2011c684, 0x7d244a85, + 0xf83dbbd2, 0x1132f9ae, 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc, + 0xec52860d, 0xd0e3c177, 0x6c16b32b, 0x99b970a9, 0xfa489411, + 0x2264e947, 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, 0xef903322, + 0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498, 0xcf81f5a6, + 0x28de7aa5, 0x268eb7da, 0xa4bfad3f, 0xe49d3a2c, 0x0d927850, + 0x9bcc5f6a, 0x62467e54, 0xc2138df6, 0xe8b8d890, 0x5ef7392e, + 0xf5afc382, 0xbe805d9f, 0x7c93d069, 0xa92dd56f, 0xb31225cf, + 0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb, 0x097826cd, + 0xf418596e, 0x01b79aec, 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa, + 0x08cfbc21, 0xe6e815ef, 0xd99be7ba, 0xce366f4a, 0xd4099fea, + 0xd67cb029, 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235, + 0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733, 0x4a9804f1, + 0xf7daec41, 0x0e50cd7f, 0x2ff69117, 0x8dd64d76, 0x4db0ef43, + 0x544daacc, 0xdf0496e4, 0xe3b5d19e, 0x1b886a4c, 0xb81f2cc1, + 0x7f516546, 0x04ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb, + 0x5a1d67b3, 0x52d2db92, 0x335610e9, 0x1347d66d, 0x8c61d79a, + 0x7a0ca137, 0x8e14f859, 0x893c13eb, 0xee27a9ce, 0x35c961b7, + 0xede51ce1, 0x3cb1477a, 0x59dfd29c, 0x3f73f255, 0x79ce1418, + 0xbf37c773, 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478, + 0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2, 0x72c31d16, + 0x0c25e2bc, 0x8b493c28, 0x41950dff, 0x7101a839, 0xdeb30c08, + 0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48, + 0x4257b8d0}; + + private static final int[] Tinv1 = + { + 0xa7f45150, 0x65417e53, 0xa4171ac3, 0x5e273a96, 0x6bab3bcb, + 0x459d1ff1, 0x58faacab, 0x03e34b93, 0xfa302055, 0x6d76adf6, + 0x76cc8891, 0x4c02f525, 0xd7e54ffc, 0xcb2ac5d7, 0x44352680, + 0xa362b58f, 0x5ab1de49, 0x1bba2567, 0x0eea4598, 0xc0fe5de1, + 0x752fc302, 0xf04c8112, 0x97468da3, 0xf9d36bc6, 0x5f8f03e7, + 0x9c921595, 0x7a6dbfeb, 0x595295da, 0x83bed42d, 0x217458d3, + 0x69e04929, 0xc8c98e44, 0x89c2756a, 0x798ef478, 0x3e58996b, + 0x71b927dd, 0x4fe1beb6, 0xad88f017, 0xac20c966, 0x3ace7db4, + 0x4adf6318, 0x311ae582, 0x33519760, 0x7f536245, 0x7764b1e0, + 0xae6bbb84, 0xa081fe1c, 0x2b08f994, 0x68487058, 0xfd458f19, + 0x6cde9487, 0xf87b52b7, 0xd373ab23, 0x024b72e2, 0x8f1fe357, + 0xab55662a, 0x28ebb207, 0xc2b52f03, 0x7bc5869a, 0x0837d3a5, + 0x872830f2, 0xa5bf23b2, 0x6a0302ba, 0x8216ed5c, 0x1ccf8a2b, + 0xb479a792, 0xf207f3f0, 0xe2694ea1, 0xf4da65cd, 0xbe0506d5, + 0x6234d11f, 0xfea6c48a, 0x532e349d, 0x55f3a2a0, 0xe18a0532, + 0xebf6a475, 0xec830b39, 0xef6040aa, 0x9f715e06, 0x106ebd51, + 0x8a213ef9, 0x06dd963d, 0x053eddae, 0xbde64d46, 0x8d5491b5, + 0x5dc47105, 0xd406046f, 0x155060ff, 0xfb981924, 0xe9bdd697, + 0x434089cc, 0x9ed96777, 0x42e8b0bd, 0x8b890788, 0x5b19e738, + 0xeec879db, 0x0a7ca147, 0x0f427ce9, 0x1e84f8c9, 0x00000000, + 0x86800983, 0xed2b3248, 0x70111eac, 0x725a6c4e, 0xff0efdfb, + 0x38850f56, 0xd5ae3d1e, 0x392d3627, 0xd90f0a64, 0xa65c6821, + 0x545b9bd1, 0x2e36243a, 0x670a0cb1, 0xe757930f, 0x96eeb4d2, + 0x919b1b9e, 0xc5c0804f, 0x20dc61a2, 0x4b775a69, 0x1a121c16, + 0xba93e20a, 0x2aa0c0e5, 0xe0223c43, 0x171b121d, 0x0d090e0b, + 0xc78bf2ad, 0xa8b62db9, 0xa91e14c8, 0x19f15785, 0x0775af4c, + 0xdd99eebb, 0x607fa3fd, 0x2601f79f, 0xf5725cbc, 0x3b6644c5, + 0x7efb5b34, 0x29438b76, 0xc623cbdc, 0xfcedb668, 0xf1e4b863, + 0xdc31d7ca, 0x85634210, 0x22971340, 0x11c68420, 0x244a857d, + 0x3dbbd2f8, 0x32f9ae11, 0xa129c76d, 0x2f9e1d4b, 0x30b2dcf3, + 0x52860dec, 0xe3c177d0, 0x16b32b6c, 0xb970a999, 0x489411fa, + 0x64e94722, 0x8cfca8c4, 0x3ff0a01a, 0x2c7d56d8, 0x903322ef, + 0x4e4987c7, 0xd138d9c1, 0xa2ca8cfe, 0x0bd49836, 0x81f5a6cf, + 0xde7aa528, 0x8eb7da26, 0xbfad3fa4, 0x9d3a2ce4, 0x9278500d, + 0xcc5f6a9b, 0x467e5462, 0x138df6c2, 0xb8d890e8, 0xf7392e5e, + 0xafc382f5, 0x805d9fbe, 0x93d0697c, 0x2dd56fa9, 0x1225cfb3, + 0x99acc83b, 0x7d1810a7, 0x639ce86e, 0xbb3bdb7b, 0x7826cd09, + 0x18596ef4, 0xb79aec01, 0x9a4f83a8, 0x6e95e665, 0xe6ffaa7e, + 0xcfbc2108, 0xe815efe6, 0x9be7bad9, 0x366f4ace, 0x099fead4, + 0x7cb029d6, 0xb2a431af, 0x233f2a31, 0x94a5c630, 0x66a235c0, + 0xbc4e7437, 0xca82fca6, 0xd090e0b0, 0xd8a73315, 0x9804f14a, + 0xdaec41f7, 0x50cd7f0e, 0xf691172f, 0xd64d768d, 0xb0ef434d, + 0x4daacc54, 0x0496e4df, 0xb5d19ee3, 0x886a4c1b, 0x1f2cc1b8, + 0x5165467f, 0xea5e9d04, 0x358c015d, 0x7487fa73, 0x410bfb2e, + 0x1d67b35a, 0xd2db9252, 0x5610e933, 0x47d66d13, 0x61d79a8c, + 0x0ca1377a, 0x14f8598e, 0x3c13eb89, 0x27a9ceee, 0xc961b735, + 0xe51ce1ed, 0xb1477a3c, 0xdfd29c59, 0x73f2553f, 0xce141879, + 0x37c773bf, 0xcdf753ea, 0xaafd5f5b, 0x6f3ddf14, 0xdb447886, + 0xf3afca81, 0xc468b93e, 0x3424382c, 0x40a3c25f, 0xc31d1672, + 0x25e2bc0c, 0x493c288b, 0x950dff41, 0x01a83971, 0xb30c08de, + 0xe4b4d89c, 0xc1566490, 0x84cb7b61, 0xb632d570, 0x5c6c4874, + 0x57b8d042}; + + private static final int[] Tinv2 = + { + 0xf45150a7, 0x417e5365, 0x171ac3a4, 0x273a965e, 0xab3bcb6b, + 0x9d1ff145, 0xfaacab58, 0xe34b9303, 0x302055fa, 0x76adf66d, + 0xcc889176, 0x02f5254c, 0xe54ffcd7, 0x2ac5d7cb, 0x35268044, + 0x62b58fa3, 0xb1de495a, 0xba25671b, 0xea45980e, 0xfe5de1c0, + 0x2fc30275, 0x4c8112f0, 0x468da397, 0xd36bc6f9, 0x8f03e75f, + 0x9215959c, 0x6dbfeb7a, 0x5295da59, 0xbed42d83, 0x7458d321, + 0xe0492969, 0xc98e44c8, 0xc2756a89, 0x8ef47879, 0x58996b3e, + 0xb927dd71, 0xe1beb64f, 0x88f017ad, 0x20c966ac, 0xce7db43a, + 0xdf63184a, 0x1ae58231, 0x51976033, 0x5362457f, 0x64b1e077, + 0x6bbb84ae, 0x81fe1ca0, 0x08f9942b, 0x48705868, 0x458f19fd, + 0xde94876c, 0x7b52b7f8, 0x73ab23d3, 0x4b72e202, 0x1fe3578f, + 0x55662aab, 0xebb20728, 0xb52f03c2, 0xc5869a7b, 0x37d3a508, + 0x2830f287, 0xbf23b2a5, 0x0302ba6a, 0x16ed5c82, 0xcf8a2b1c, + 0x79a792b4, 0x07f3f0f2, 0x694ea1e2, 0xda65cdf4, 0x0506d5be, + 0x34d11f62, 0xa6c48afe, 0x2e349d53, 0xf3a2a055, 0x8a0532e1, + 0xf6a475eb, 0x830b39ec, 0x6040aaef, 0x715e069f, 0x6ebd5110, + 0x213ef98a, 0xdd963d06, 0x3eddae05, 0xe64d46bd, 0x5491b58d, + 0xc471055d, 0x06046fd4, 0x5060ff15, 0x981924fb, 0xbdd697e9, + 0x4089cc43, 0xd967779e, 0xe8b0bd42, 0x8907888b, 0x19e7385b, + 0xc879dbee, 0x7ca1470a, 0x427ce90f, 0x84f8c91e, 0x00000000, + 0x80098386, 0x2b3248ed, 0x111eac70, 0x5a6c4e72, 0x0efdfbff, + 0x850f5638, 0xae3d1ed5, 0x2d362739, 0x0f0a64d9, 0x5c6821a6, + 0x5b9bd154, 0x36243a2e, 0x0a0cb167, 0x57930fe7, 0xeeb4d296, + 0x9b1b9e91, 0xc0804fc5, 0xdc61a220, 0x775a694b, 0x121c161a, + 0x93e20aba, 0xa0c0e52a, 0x223c43e0, 0x1b121d17, 0x090e0b0d, + 0x8bf2adc7, 0xb62db9a8, 0x1e14c8a9, 0xf1578519, 0x75af4c07, + 0x99eebbdd, 0x7fa3fd60, 0x01f79f26, 0x725cbcf5, 0x6644c53b, + 0xfb5b347e, 0x438b7629, 0x23cbdcc6, 0xedb668fc, 0xe4b863f1, + 0x31d7cadc, 0x63421085, 0x97134022, 0xc6842011, 0x4a857d24, + 0xbbd2f83d, 0xf9ae1132, 0x29c76da1, 0x9e1d4b2f, 0xb2dcf330, + 0x860dec52, 0xc177d0e3, 0xb32b6c16, 0x70a999b9, 0x9411fa48, + 0xe9472264, 0xfca8c48c, 0xf0a01a3f, 0x7d56d82c, 0x3322ef90, + 0x4987c74e, 0x38d9c1d1, 0xca8cfea2, 0xd498360b, 0xf5a6cf81, + 0x7aa528de, 0xb7da268e, 0xad3fa4bf, 0x3a2ce49d, 0x78500d92, + 0x5f6a9bcc, 0x7e546246, 0x8df6c213, 0xd890e8b8, 0x392e5ef7, + 0xc382f5af, 0x5d9fbe80, 0xd0697c93, 0xd56fa92d, 0x25cfb312, + 0xacc83b99, 0x1810a77d, 0x9ce86e63, 0x3bdb7bbb, 0x26cd0978, + 0x596ef418, 0x9aec01b7, 0x4f83a89a, 0x95e6656e, 0xffaa7ee6, + 0xbc2108cf, 0x15efe6e8, 0xe7bad99b, 0x6f4ace36, 0x9fead409, + 0xb029d67c, 0xa431afb2, 0x3f2a3123, 0xa5c63094, 0xa235c066, + 0x4e7437bc, 0x82fca6ca, 0x90e0b0d0, 0xa73315d8, 0x04f14a98, + 0xec41f7da, 0xcd7f0e50, 0x91172ff6, 0x4d768dd6, 0xef434db0, + 0xaacc544d, 0x96e4df04, 0xd19ee3b5, 0x6a4c1b88, 0x2cc1b81f, + 0x65467f51, 0x5e9d04ea, 0x8c015d35, 0x87fa7374, 0x0bfb2e41, + 0x67b35a1d, 0xdb9252d2, 0x10e93356, 0xd66d1347, 0xd79a8c61, + 0xa1377a0c, 0xf8598e14, 0x13eb893c, 0xa9ceee27, 0x61b735c9, + 0x1ce1ede5, 0x477a3cb1, 0xd29c59df, 0xf2553f73, 0x141879ce, + 0xc773bf37, 0xf753eacd, 0xfd5f5baa, 0x3ddf146f, 0x447886db, + 0xafca81f3, 0x68b93ec4, 0x24382c34, 0xa3c25f40, 0x1d1672c3, + 0xe2bc0c25, 0x3c288b49, 0x0dff4195, 0xa8397101, 0x0c08deb3, + 0xb4d89ce4, 0x566490c1, 0xcb7b6184, 0x32d570b6, 0x6c48745c, + 0xb8d04257}; + + private static final int[] Tinv3 = + { + 0x5150a7f4, 0x7e536541, 0x1ac3a417, 0x3a965e27, 0x3bcb6bab, + 0x1ff1459d, 0xacab58fa, 0x4b9303e3, 0x2055fa30, 0xadf66d76, + 0x889176cc, 0xf5254c02, 0x4ffcd7e5, 0xc5d7cb2a, 0x26804435, + 0xb58fa362, 0xde495ab1, 0x25671bba, 0x45980eea, 0x5de1c0fe, + 0xc302752f, 0x8112f04c, 0x8da39746, 0x6bc6f9d3, 0x03e75f8f, + 0x15959c92, 0xbfeb7a6d, 0x95da5952, 0xd42d83be, 0x58d32174, + 0x492969e0, 0x8e44c8c9, 0x756a89c2, 0xf478798e, 0x996b3e58, + 0x27dd71b9, 0xbeb64fe1, 0xf017ad88, 0xc966ac20, 0x7db43ace, + 0x63184adf, 0xe582311a, 0x97603351, 0x62457f53, 0xb1e07764, + 0xbb84ae6b, 0xfe1ca081, 0xf9942b08, 0x70586848, 0x8f19fd45, + 0x94876cde, 0x52b7f87b, 0xab23d373, 0x72e2024b, 0xe3578f1f, + 0x662aab55, 0xb20728eb, 0x2f03c2b5, 0x869a7bc5, 0xd3a50837, + 0x30f28728, 0x23b2a5bf, 0x02ba6a03, 0xed5c8216, 0x8a2b1ccf, + 0xa792b479, 0xf3f0f207, 0x4ea1e269, 0x65cdf4da, 0x06d5be05, + 0xd11f6234, 0xc48afea6, 0x349d532e, 0xa2a055f3, 0x0532e18a, + 0xa475ebf6, 0x0b39ec83, 0x40aaef60, 0x5e069f71, 0xbd51106e, + 0x3ef98a21, 0x963d06dd, 0xddae053e, 0x4d46bde6, 0x91b58d54, + 0x71055dc4, 0x046fd406, 0x60ff1550, 0x1924fb98, 0xd697e9bd, + 0x89cc4340, 0x67779ed9, 0xb0bd42e8, 0x07888b89, 0xe7385b19, + 0x79dbeec8, 0xa1470a7c, 0x7ce90f42, 0xf8c91e84, 0x00000000, + 0x09838680, 0x3248ed2b, 0x1eac7011, 0x6c4e725a, 0xfdfbff0e, + 0x0f563885, 0x3d1ed5ae, 0x3627392d, 0x0a64d90f, 0x6821a65c, + 0x9bd1545b, 0x243a2e36, 0x0cb1670a, 0x930fe757, 0xb4d296ee, + 0x1b9e919b, 0x804fc5c0, 0x61a220dc, 0x5a694b77, 0x1c161a12, + 0xe20aba93, 0xc0e52aa0, 0x3c43e022, 0x121d171b, 0x0e0b0d09, + 0xf2adc78b, 0x2db9a8b6, 0x14c8a91e, 0x578519f1, 0xaf4c0775, + 0xeebbdd99, 0xa3fd607f, 0xf79f2601, 0x5cbcf572, 0x44c53b66, + 0x5b347efb, 0x8b762943, 0xcbdcc623, 0xb668fced, 0xb863f1e4, + 0xd7cadc31, 0x42108563, 0x13402297, 0x842011c6, 0x857d244a, + 0xd2f83dbb, 0xae1132f9, 0xc76da129, 0x1d4b2f9e, 0xdcf330b2, + 0x0dec5286, 0x77d0e3c1, 0x2b6c16b3, 0xa999b970, 0x11fa4894, + 0x472264e9, 0xa8c48cfc, 0xa01a3ff0, 0x56d82c7d, 0x22ef9033, + 0x87c74e49, 0xd9c1d138, 0x8cfea2ca, 0x98360bd4, 0xa6cf81f5, + 0xa528de7a, 0xda268eb7, 0x3fa4bfad, 0x2ce49d3a, 0x500d9278, + 0x6a9bcc5f, 0x5462467e, 0xf6c2138d, 0x90e8b8d8, 0x2e5ef739, + 0x82f5afc3, 0x9fbe805d, 0x697c93d0, 0x6fa92dd5, 0xcfb31225, + 0xc83b99ac, 0x10a77d18, 0xe86e639c, 0xdb7bbb3b, 0xcd097826, + 0x6ef41859, 0xec01b79a, 0x83a89a4f, 0xe6656e95, 0xaa7ee6ff, + 0x2108cfbc, 0xefe6e815, 0xbad99be7, 0x4ace366f, 0xead4099f, + 0x29d67cb0, 0x31afb2a4, 0x2a31233f, 0xc63094a5, 0x35c066a2, + 0x7437bc4e, 0xfca6ca82, 0xe0b0d090, 0x3315d8a7, 0xf14a9804, + 0x41f7daec, 0x7f0e50cd, 0x172ff691, 0x768dd64d, 0x434db0ef, + 0xcc544daa, 0xe4df0496, 0x9ee3b5d1, 0x4c1b886a, 0xc1b81f2c, + 0x467f5165, 0x9d04ea5e, 0x015d358c, 0xfa737487, 0xfb2e410b, + 0xb35a1d67, 0x9252d2db, 0xe9335610, 0x6d1347d6, 0x9a8c61d7, + 0x377a0ca1, 0x598e14f8, 0xeb893c13, 0xceee27a9, 0xb735c961, + 0xe1ede51c, 0x7a3cb147, 0x9c59dfd2, 0x553f73f2, 0x1879ce14, + 0x73bf37c7, 0x53eacdf7, 0x5f5baafd, 0xdf146f3d, 0x7886db44, + 0xca81f3af, 0xb93ec468, 0x382c3424, 0xc25f40a3, 0x1672c31d, + 0xbc0c25e2, 0x288b493c, 0xff41950d, 0x397101a8, 0x08deb30c, + 0xd89ce4b4, 0x6490c156, 0x7b6184cb, 0xd570b632, 0x48745c6c, + 0xd04257b8}; + + private int shift( + int r, + int shift) + { + return (r >>> shift) | (r << -shift); + } + + /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ + + private static final int m1 = 0x80808080; + private static final int m2 = 0x7f7f7f7f; + private static final int m3 = 0x0000001b; + + private int FFmulX(int x) + { + return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3)); + } + + /* + The following defines provide alternative definitions of FFmulX that might + give improved performance if a fast 32-bit multiply is not available. + + private int FFmulX(int x) { int u = x & m1; u |= (u >> 1); return ((x & m2) << 1) ^ ((u >>> 3) | (u >>> 6)); } + private static final int m4 = 0x1b1b1b1b; + private int FFmulX(int x) { int u = x & m1; return ((x & m2) << 1) ^ ((u - (u >>> 7)) & m4); } + + */ + + private int inv_mcol(int x) + { + int f2 = FFmulX(x); + int f4 = FFmulX(f2); + int f8 = FFmulX(f4); + int f9 = x ^ f8; + + return f2 ^ f4 ^ f8 ^ shift(f2 ^ f9, 8) ^ shift(f4 ^ f9, 16) ^ shift(f9, 24); + } + + + private int subWord(int x) + { + return (S[x&255]&255 | ((S[(x>>8)&255]&255)<<8) | ((S[(x>>16)&255]&255)<<16) | S[(x>>24)&255]<<24); + } + + /** + * Calculate the necessary round keys + * The number of calculations depends on key size and block size + * AES specified a fixed block size of 128 bits and key sizes 128/192/256 bits + * This code is written assuming those are the only possible values + */ + private int[][] generateWorkingKey( + byte[] key, + boolean forEncryption) + { + int KC = key.length / 4; // key length in words + int t; + + if (((KC != 4) && (KC != 6) && (KC != 8)) || ((KC * 4) != key.length)) + { + throw new IllegalArgumentException("Key length not 128/192/256 bits."); + } + + ROUNDS = KC + 6; // This is not always true for the generalized Rijndael that allows larger block sizes + int[][] W = new int[ROUNDS+1][4]; // 4 words in a block + + // + // copy the key into the round key array + // + + t = 0; + int i = 0; + while (i < key.length) + { + W[t >> 2][t & 3] = (key[i]&0xff) | ((key[i+1]&0xff) << 8) | ((key[i+2]&0xff) << 16) | (key[i+3] << 24); + i+=4; + t++; + } + + // + // while not enough round key material calculated + // calculate new values + // + int k = (ROUNDS + 1) << 2; + for (i = KC; (i < k); i++) + { + int temp = W[(i - 1) >> 2][(i - 1) & 3]; + if ((i % KC) == 0) + { + temp = subWord(shift(temp, 8)) ^ rcon[(i / KC) - 1]; + } + else if ((KC > 6) && ((i % KC) == 4)) + { + temp = subWord(temp); + } + + W[i >> 2][i & 3] = W[(i - KC) >> 2][(i - KC) & 3] ^ temp; + } + + if (!forEncryption) + { + for (int j = 1; j < ROUNDS; j++) + { + for (i = 0; i < 4; i++) + { + W[j][i] = inv_mcol(W[j][i]); + } + } + } + + return W; + } + + private int ROUNDS; + private int[][] WorkingKey = null; + private int C0, C1, C2, C3; + private boolean forEncryption; + + private static final int BLOCK_SIZE = 16; + + /** + * default constructor - 128 bit block size. + */ + public AESFastEngine() + { + } + + /** + * initialise an AES cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + WorkingKey = generateWorkingKey(((KeyParameter)params).getKey(), forEncryption); + this.forEncryption = forEncryption; + return; + } + + throw new IllegalArgumentException("invalid parameter passed to AES init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "AES"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (WorkingKey == null) + { + throw new IllegalStateException("AES engine not initialised"); + } + + if ((inOff + (32 / 2)) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + (32 / 2)) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (forEncryption) + { + unpackBlock(in, inOff); + encryptBlock(WorkingKey); + packBlock(out, outOff); + } + else + { + unpackBlock(in, inOff); + decryptBlock(WorkingKey); + packBlock(out, outOff); + } + + return BLOCK_SIZE; + } + + public void reset() + { + } + + private void unpackBlock( + byte[] bytes, + int off) + { + int index = off; + + C0 = (bytes[index++] & 0xff); + C0 |= (bytes[index++] & 0xff) << 8; + C0 |= (bytes[index++] & 0xff) << 16; + C0 |= bytes[index++] << 24; + + C1 = (bytes[index++] & 0xff); + C1 |= (bytes[index++] & 0xff) << 8; + C1 |= (bytes[index++] & 0xff) << 16; + C1 |= bytes[index++] << 24; + + C2 = (bytes[index++] & 0xff); + C2 |= (bytes[index++] & 0xff) << 8; + C2 |= (bytes[index++] & 0xff) << 16; + C2 |= bytes[index++] << 24; + + C3 = (bytes[index++] & 0xff); + C3 |= (bytes[index++] & 0xff) << 8; + C3 |= (bytes[index++] & 0xff) << 16; + C3 |= bytes[index++] << 24; + } + + private void packBlock( + byte[] bytes, + int off) + { + int index = off; + + bytes[index++] = (byte)C0; + bytes[index++] = (byte)(C0 >> 8); + bytes[index++] = (byte)(C0 >> 16); + bytes[index++] = (byte)(C0 >> 24); + + bytes[index++] = (byte)C1; + bytes[index++] = (byte)(C1 >> 8); + bytes[index++] = (byte)(C1 >> 16); + bytes[index++] = (byte)(C1 >> 24); + + bytes[index++] = (byte)C2; + bytes[index++] = (byte)(C2 >> 8); + bytes[index++] = (byte)(C2 >> 16); + bytes[index++] = (byte)(C2 >> 24); + + bytes[index++] = (byte)C3; + bytes[index++] = (byte)(C3 >> 8); + bytes[index++] = (byte)(C3 >> 16); + bytes[index++] = (byte)(C3 >> 24); + } + + private void encryptBlock(int[][] KW) + { + int r, r0, r1, r2, r3; + + C0 ^= KW[0][0]; + C1 ^= KW[0][1]; + C2 ^= KW[0][2]; + C3 ^= KW[0][3]; + + r = 1; + while (r < ROUNDS - 1) + { + r0 = T0[C0&255] ^ T1[(C1>>8)&255] ^ T2[(C2>>16)&255] ^ T3[(C3>>24)&255] ^ KW[r][0]; + r1 = T0[C1&255] ^ T1[(C2>>8)&255] ^ T2[(C3>>16)&255] ^ T3[(C0>>24)&255] ^ KW[r][1]; + r2 = T0[C2&255] ^ T1[(C3>>8)&255] ^ T2[(C0>>16)&255] ^ T3[(C1>>24)&255] ^ KW[r][2]; + r3 = T0[C3&255] ^ T1[(C0>>8)&255] ^ T2[(C1>>16)&255] ^ T3[(C2>>24)&255] ^ KW[r++][3]; + C0 = T0[r0&255] ^ T1[(r1>>8)&255] ^ T2[(r2>>16)&255] ^ T3[(r3>>24)&255] ^ KW[r][0]; + C1 = T0[r1&255] ^ T1[(r2>>8)&255] ^ T2[(r3>>16)&255] ^ T3[(r0>>24)&255] ^ KW[r][1]; + C2 = T0[r2&255] ^ T1[(r3>>8)&255] ^ T2[(r0>>16)&255] ^ T3[(r1>>24)&255] ^ KW[r][2]; + C3 = T0[r3&255] ^ T1[(r0>>8)&255] ^ T2[(r1>>16)&255] ^ T3[(r2>>24)&255] ^ KW[r++][3]; + } + + r0 = T0[C0&255] ^ T1[(C1>>8)&255] ^ T2[(C2>>16)&255] ^ T3[(C3>>24)&255] ^ KW[r][0]; + r1 = T0[C1&255] ^ T1[(C2>>8)&255] ^ T2[(C3>>16)&255] ^ T3[(C0>>24)&255] ^ KW[r][1]; + r2 = T0[C2&255] ^ T1[(C3>>8)&255] ^ T2[(C0>>16)&255] ^ T3[(C1>>24)&255] ^ KW[r][2]; + r3 = T0[C3&255] ^ T1[(C0>>8)&255] ^ T2[(C1>>16)&255] ^ T3[(C2>>24)&255] ^ KW[r++][3]; + + // the final round's table is a simple function of S so we don't use a whole other four tables for it + + C0 = (S[r0&255]&255) ^ ((S[(r1>>8)&255]&255)<<8) ^ ((S[(r2>>16)&255]&255)<<16) ^ (S[(r3>>24)&255]<<24) ^ KW[r][0]; + C1 = (S[r1&255]&255) ^ ((S[(r2>>8)&255]&255)<<8) ^ ((S[(r3>>16)&255]&255)<<16) ^ (S[(r0>>24)&255]<<24) ^ KW[r][1]; + C2 = (S[r2&255]&255) ^ ((S[(r3>>8)&255]&255)<<8) ^ ((S[(r0>>16)&255]&255)<<16) ^ (S[(r1>>24)&255]<<24) ^ KW[r][2]; + C3 = (S[r3&255]&255) ^ ((S[(r0>>8)&255]&255)<<8) ^ ((S[(r1>>16)&255]&255)<<16) ^ (S[(r2>>24)&255]<<24) ^ KW[r][3]; + + } + + private void decryptBlock(int[][] KW) + { + int r0, r1, r2, r3; + + C0 ^= KW[ROUNDS][0]; + C1 ^= KW[ROUNDS][1]; + C2 ^= KW[ROUNDS][2]; + C3 ^= KW[ROUNDS][3]; + + int r = ROUNDS-1; + + while (r>1) + { + r0 = Tinv0[C0&255] ^ Tinv1[(C3>>8)&255] ^ Tinv2[(C2>>16)&255] ^ Tinv3[(C1>>24)&255] ^ KW[r][0]; + r1 = Tinv0[C1&255] ^ Tinv1[(C0>>8)&255] ^ Tinv2[(C3>>16)&255] ^ Tinv3[(C2>>24)&255] ^ KW[r][1]; + r2 = Tinv0[C2&255] ^ Tinv1[(C1>>8)&255] ^ Tinv2[(C0>>16)&255] ^ Tinv3[(C3>>24)&255] ^ KW[r][2]; + r3 = Tinv0[C3&255] ^ Tinv1[(C2>>8)&255] ^ Tinv2[(C1>>16)&255] ^ Tinv3[(C0>>24)&255] ^ KW[r--][3]; + C0 = Tinv0[r0&255] ^ Tinv1[(r3>>8)&255] ^ Tinv2[(r2>>16)&255] ^ Tinv3[(r1>>24)&255] ^ KW[r][0]; + C1 = Tinv0[r1&255] ^ Tinv1[(r0>>8)&255] ^ Tinv2[(r3>>16)&255] ^ Tinv3[(r2>>24)&255] ^ KW[r][1]; + C2 = Tinv0[r2&255] ^ Tinv1[(r1>>8)&255] ^ Tinv2[(r0>>16)&255] ^ Tinv3[(r3>>24)&255] ^ KW[r][2]; + C3 = Tinv0[r3&255] ^ Tinv1[(r2>>8)&255] ^ Tinv2[(r1>>16)&255] ^ Tinv3[(r0>>24)&255] ^ KW[r--][3]; + } + + r0 = Tinv0[C0&255] ^ Tinv1[(C3>>8)&255] ^ Tinv2[(C2>>16)&255] ^ Tinv3[(C1>>24)&255] ^ KW[r][0]; + r1 = Tinv0[C1&255] ^ Tinv1[(C0>>8)&255] ^ Tinv2[(C3>>16)&255] ^ Tinv3[(C2>>24)&255] ^ KW[r][1]; + r2 = Tinv0[C2&255] ^ Tinv1[(C1>>8)&255] ^ Tinv2[(C0>>16)&255] ^ Tinv3[(C3>>24)&255] ^ KW[r][2]; + r3 = Tinv0[C3&255] ^ Tinv1[(C2>>8)&255] ^ Tinv2[(C1>>16)&255] ^ Tinv3[(C0>>24)&255] ^ KW[r][3]; + + // the final round's table is a simple function of Si so we don't use a whole other four tables for it + + C0 = (Si[r0&255]&255) ^ ((Si[(r3>>8)&255]&255)<<8) ^ ((Si[(r2>>16)&255]&255)<<16) ^ (Si[(r1>>24)&255]<<24) ^ KW[0][0]; + C1 = (Si[r1&255]&255) ^ ((Si[(r0>>8)&255]&255)<<8) ^ ((Si[(r3>>16)&255]&255)<<16) ^ (Si[(r2>>24)&255]<<24) ^ KW[0][1]; + C2 = (Si[r2&255]&255) ^ ((Si[(r1>>8)&255]&255)<<8) ^ ((Si[(r0>>16)&255]&255)<<16) ^ (Si[(r3>>24)&255]<<24) ^ KW[0][2]; + C3 = (Si[r3&255]&255) ^ ((Si[(r2>>8)&255]&255)<<8) ^ ((Si[(r1>>16)&255]&255)<<16) ^ (Si[(r0>>24)&255]<<24) ^ KW[0][3]; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/AESLightEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/AESLightEngine.java new file mode 100644 index 000000000..b62c0244f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/AESLightEngine.java @@ -0,0 +1,440 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * an implementation of the AES (Rijndael), from FIPS-197. + *

+ * For further details see: http://csrc.nist.gov/encryption/aes/. + * + * This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at + * http://fp.gladman.plus.com/cryptography_technology/rijndael/ + * + * There are three levels of tradeoff of speed vs memory + * Because java has no preprocessor, they are written as three separate classes from which to choose + * + * The fastest uses 8Kbytes of static tables to precompute round calculations, 4 256 word tables for encryption + * and 4 for decryption. + * + * The middle performance version uses only one 256 word table for each, for a total of 2Kbytes, + * adding 12 rotate operations per round to compute the values contained in the other tables from + * the contents of the first + * + * The slowest version uses no static tables at all and computes the values + * in each round. + *

+ * This file contains the slowest performance version with no static tables + * for round precomputation, but it has the smallest foot print. + * + */ +public class AESLightEngine + implements BlockCipher +{ + // The S box + private static final byte[] S = { + (byte)99, (byte)124, (byte)119, (byte)123, (byte)242, (byte)107, (byte)111, (byte)197, + (byte)48, (byte)1, (byte)103, (byte)43, (byte)254, (byte)215, (byte)171, (byte)118, + (byte)202, (byte)130, (byte)201, (byte)125, (byte)250, (byte)89, (byte)71, (byte)240, + (byte)173, (byte)212, (byte)162, (byte)175, (byte)156, (byte)164, (byte)114, (byte)192, + (byte)183, (byte)253, (byte)147, (byte)38, (byte)54, (byte)63, (byte)247, (byte)204, + (byte)52, (byte)165, (byte)229, (byte)241, (byte)113, (byte)216, (byte)49, (byte)21, + (byte)4, (byte)199, (byte)35, (byte)195, (byte)24, (byte)150, (byte)5, (byte)154, + (byte)7, (byte)18, (byte)128, (byte)226, (byte)235, (byte)39, (byte)178, (byte)117, + (byte)9, (byte)131, (byte)44, (byte)26, (byte)27, (byte)110, (byte)90, (byte)160, + (byte)82, (byte)59, (byte)214, (byte)179, (byte)41, (byte)227, (byte)47, (byte)132, + (byte)83, (byte)209, (byte)0, (byte)237, (byte)32, (byte)252, (byte)177, (byte)91, + (byte)106, (byte)203, (byte)190, (byte)57, (byte)74, (byte)76, (byte)88, (byte)207, + (byte)208, (byte)239, (byte)170, (byte)251, (byte)67, (byte)77, (byte)51, (byte)133, + (byte)69, (byte)249, (byte)2, (byte)127, (byte)80, (byte)60, (byte)159, (byte)168, + (byte)81, (byte)163, (byte)64, (byte)143, (byte)146, (byte)157, (byte)56, (byte)245, + (byte)188, (byte)182, (byte)218, (byte)33, (byte)16, (byte)255, (byte)243, (byte)210, + (byte)205, (byte)12, (byte)19, (byte)236, (byte)95, (byte)151, (byte)68, (byte)23, + (byte)196, (byte)167, (byte)126, (byte)61, (byte)100, (byte)93, (byte)25, (byte)115, + (byte)96, (byte)129, (byte)79, (byte)220, (byte)34, (byte)42, (byte)144, (byte)136, + (byte)70, (byte)238, (byte)184, (byte)20, (byte)222, (byte)94, (byte)11, (byte)219, + (byte)224, (byte)50, (byte)58, (byte)10, (byte)73, (byte)6, (byte)36, (byte)92, + (byte)194, (byte)211, (byte)172, (byte)98, (byte)145, (byte)149, (byte)228, (byte)121, + (byte)231, (byte)200, (byte)55, (byte)109, (byte)141, (byte)213, (byte)78, (byte)169, + (byte)108, (byte)86, (byte)244, (byte)234, (byte)101, (byte)122, (byte)174, (byte)8, + (byte)186, (byte)120, (byte)37, (byte)46, (byte)28, (byte)166, (byte)180, (byte)198, + (byte)232, (byte)221, (byte)116, (byte)31, (byte)75, (byte)189, (byte)139, (byte)138, + (byte)112, (byte)62, (byte)181, (byte)102, (byte)72, (byte)3, (byte)246, (byte)14, + (byte)97, (byte)53, (byte)87, (byte)185, (byte)134, (byte)193, (byte)29, (byte)158, + (byte)225, (byte)248, (byte)152, (byte)17, (byte)105, (byte)217, (byte)142, (byte)148, + (byte)155, (byte)30, (byte)135, (byte)233, (byte)206, (byte)85, (byte)40, (byte)223, + (byte)140, (byte)161, (byte)137, (byte)13, (byte)191, (byte)230, (byte)66, (byte)104, + (byte)65, (byte)153, (byte)45, (byte)15, (byte)176, (byte)84, (byte)187, (byte)22, + }; + + // The inverse S-box + private static final byte[] Si = { + (byte)82, (byte)9, (byte)106, (byte)213, (byte)48, (byte)54, (byte)165, (byte)56, + (byte)191, (byte)64, (byte)163, (byte)158, (byte)129, (byte)243, (byte)215, (byte)251, + (byte)124, (byte)227, (byte)57, (byte)130, (byte)155, (byte)47, (byte)255, (byte)135, + (byte)52, (byte)142, (byte)67, (byte)68, (byte)196, (byte)222, (byte)233, (byte)203, + (byte)84, (byte)123, (byte)148, (byte)50, (byte)166, (byte)194, (byte)35, (byte)61, + (byte)238, (byte)76, (byte)149, (byte)11, (byte)66, (byte)250, (byte)195, (byte)78, + (byte)8, (byte)46, (byte)161, (byte)102, (byte)40, (byte)217, (byte)36, (byte)178, + (byte)118, (byte)91, (byte)162, (byte)73, (byte)109, (byte)139, (byte)209, (byte)37, + (byte)114, (byte)248, (byte)246, (byte)100, (byte)134, (byte)104, (byte)152, (byte)22, + (byte)212, (byte)164, (byte)92, (byte)204, (byte)93, (byte)101, (byte)182, (byte)146, + (byte)108, (byte)112, (byte)72, (byte)80, (byte)253, (byte)237, (byte)185, (byte)218, + (byte)94, (byte)21, (byte)70, (byte)87, (byte)167, (byte)141, (byte)157, (byte)132, + (byte)144, (byte)216, (byte)171, (byte)0, (byte)140, (byte)188, (byte)211, (byte)10, + (byte)247, (byte)228, (byte)88, (byte)5, (byte)184, (byte)179, (byte)69, (byte)6, + (byte)208, (byte)44, (byte)30, (byte)143, (byte)202, (byte)63, (byte)15, (byte)2, + (byte)193, (byte)175, (byte)189, (byte)3, (byte)1, (byte)19, (byte)138, (byte)107, + (byte)58, (byte)145, (byte)17, (byte)65, (byte)79, (byte)103, (byte)220, (byte)234, + (byte)151, (byte)242, (byte)207, (byte)206, (byte)240, (byte)180, (byte)230, (byte)115, + (byte)150, (byte)172, (byte)116, (byte)34, (byte)231, (byte)173, (byte)53, (byte)133, + (byte)226, (byte)249, (byte)55, (byte)232, (byte)28, (byte)117, (byte)223, (byte)110, + (byte)71, (byte)241, (byte)26, (byte)113, (byte)29, (byte)41, (byte)197, (byte)137, + (byte)111, (byte)183, (byte)98, (byte)14, (byte)170, (byte)24, (byte)190, (byte)27, + (byte)252, (byte)86, (byte)62, (byte)75, (byte)198, (byte)210, (byte)121, (byte)32, + (byte)154, (byte)219, (byte)192, (byte)254, (byte)120, (byte)205, (byte)90, (byte)244, + (byte)31, (byte)221, (byte)168, (byte)51, (byte)136, (byte)7, (byte)199, (byte)49, + (byte)177, (byte)18, (byte)16, (byte)89, (byte)39, (byte)128, (byte)236, (byte)95, + (byte)96, (byte)81, (byte)127, (byte)169, (byte)25, (byte)181, (byte)74, (byte)13, + (byte)45, (byte)229, (byte)122, (byte)159, (byte)147, (byte)201, (byte)156, (byte)239, + (byte)160, (byte)224, (byte)59, (byte)77, (byte)174, (byte)42, (byte)245, (byte)176, + (byte)200, (byte)235, (byte)187, (byte)60, (byte)131, (byte)83, (byte)153, (byte)97, + (byte)23, (byte)43, (byte)4, (byte)126, (byte)186, (byte)119, (byte)214, (byte)38, + (byte)225, (byte)105, (byte)20, (byte)99, (byte)85, (byte)33, (byte)12, (byte)125, + }; + + // vector used in calculating key schedule (powers of x in GF(256)) + private static final int[] rcon = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 }; + + private int shift( + int r, + int shift) + { + return (r >>> shift) | (r << -shift); + } + + /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ + + private static final int m1 = 0x80808080; + private static final int m2 = 0x7f7f7f7f; + private static final int m3 = 0x0000001b; + + private int FFmulX(int x) + { + return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3)); + } + + /* + The following defines provide alternative definitions of FFmulX that might + give improved performance if a fast 32-bit multiply is not available. + + private int FFmulX(int x) { int u = x & m1; u |= (u >> 1); return ((x & m2) << 1) ^ ((u >>> 3) | (u >>> 6)); } + private static final int m4 = 0x1b1b1b1b; + private int FFmulX(int x) { int u = x & m1; return ((x & m2) << 1) ^ ((u - (u >>> 7)) & m4); } + + */ + + private int mcol(int x) + { + int f2 = FFmulX(x); + return f2 ^ shift(x ^ f2, 8) ^ shift(x, 16) ^ shift(x, 24); + } + + private int inv_mcol(int x) + { + int f2 = FFmulX(x); + int f4 = FFmulX(f2); + int f8 = FFmulX(f4); + int f9 = x ^ f8; + + return f2 ^ f4 ^ f8 ^ shift(f2 ^ f9, 8) ^ shift(f4 ^ f9, 16) ^ shift(f9, 24); + } + + + private int subWord(int x) + { + return (S[x&255]&255 | ((S[(x>>8)&255]&255)<<8) | ((S[(x>>16)&255]&255)<<16) | S[(x>>24)&255]<<24); + } + + /** + * Calculate the necessary round keys + * The number of calculations depends on key size and block size + * AES specified a fixed block size of 128 bits and key sizes 128/192/256 bits + * This code is written assuming those are the only possible values + */ + private int[][] generateWorkingKey( + byte[] key, + boolean forEncryption) + { + int KC = key.length / 4; // key length in words + int t; + + if (((KC != 4) && (KC != 6) && (KC != 8)) || ((KC * 4) != key.length)) + { + throw new IllegalArgumentException("Key length not 128/192/256 bits."); + } + + ROUNDS = KC + 6; // This is not always true for the generalized Rijndael that allows larger block sizes + int[][] W = new int[ROUNDS+1][4]; // 4 words in a block + + // + // copy the key into the round key array + // + + t = 0; + int i = 0; + while (i < key.length) + { + W[t >> 2][t & 3] = (key[i]&0xff) | ((key[i+1]&0xff) << 8) | ((key[i+2]&0xff) << 16) | (key[i+3] << 24); + i+=4; + t++; + } + + // + // while not enough round key material calculated + // calculate new values + // + int k = (ROUNDS + 1) << 2; + for (i = KC; (i < k); i++) + { + int temp = W[(i-1)>>2][(i-1)&3]; + if ((i % KC) == 0) + { + temp = subWord(shift(temp, 8)) ^ rcon[(i / KC)-1]; + } + else if ((KC > 6) && ((i % KC) == 4)) + { + temp = subWord(temp); + } + + W[i>>2][i&3] = W[(i - KC)>>2][(i-KC)&3] ^ temp; + } + + if (!forEncryption) + { + for (int j = 1; j < ROUNDS; j++) + { + for (i = 0; i < 4; i++) + { + W[j][i] = inv_mcol(W[j][i]); + } + } + } + + return W; + } + + private int ROUNDS; + private int[][] WorkingKey = null; + private int C0, C1, C2, C3; + private boolean forEncryption; + + private static final int BLOCK_SIZE = 16; + + /** + * default constructor - 128 bit block size. + */ + public AESLightEngine() + { + } + + /** + * initialise an AES cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + WorkingKey = generateWorkingKey(((KeyParameter)params).getKey(), forEncryption); + this.forEncryption = forEncryption; + return; + } + + throw new IllegalArgumentException("invalid parameter passed to AES init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "AES"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (WorkingKey == null) + { + throw new IllegalStateException("AES engine not initialised"); + } + + if ((inOff + (32 / 2)) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + (32 / 2)) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (forEncryption) + { + unpackBlock(in, inOff); + encryptBlock(WorkingKey); + packBlock(out, outOff); + } + else + { + unpackBlock(in, inOff); + decryptBlock(WorkingKey); + packBlock(out, outOff); + } + + return BLOCK_SIZE; + } + + public void reset() + { + } + + private void unpackBlock( + byte[] bytes, + int off) + { + int index = off; + + C0 = (bytes[index++] & 0xff); + C0 |= (bytes[index++] & 0xff) << 8; + C0 |= (bytes[index++] & 0xff) << 16; + C0 |= bytes[index++] << 24; + + C1 = (bytes[index++] & 0xff); + C1 |= (bytes[index++] & 0xff) << 8; + C1 |= (bytes[index++] & 0xff) << 16; + C1 |= bytes[index++] << 24; + + C2 = (bytes[index++] & 0xff); + C2 |= (bytes[index++] & 0xff) << 8; + C2 |= (bytes[index++] & 0xff) << 16; + C2 |= bytes[index++] << 24; + + C3 = (bytes[index++] & 0xff); + C3 |= (bytes[index++] & 0xff) << 8; + C3 |= (bytes[index++] & 0xff) << 16; + C3 |= bytes[index++] << 24; + } + + private void packBlock( + byte[] bytes, + int off) + { + int index = off; + + bytes[index++] = (byte)C0; + bytes[index++] = (byte)(C0 >> 8); + bytes[index++] = (byte)(C0 >> 16); + bytes[index++] = (byte)(C0 >> 24); + + bytes[index++] = (byte)C1; + bytes[index++] = (byte)(C1 >> 8); + bytes[index++] = (byte)(C1 >> 16); + bytes[index++] = (byte)(C1 >> 24); + + bytes[index++] = (byte)C2; + bytes[index++] = (byte)(C2 >> 8); + bytes[index++] = (byte)(C2 >> 16); + bytes[index++] = (byte)(C2 >> 24); + + bytes[index++] = (byte)C3; + bytes[index++] = (byte)(C3 >> 8); + bytes[index++] = (byte)(C3 >> 16); + bytes[index++] = (byte)(C3 >> 24); + } + + private void encryptBlock(int[][] KW) + { + int r, r0, r1, r2, r3; + + C0 ^= KW[0][0]; + C1 ^= KW[0][1]; + C2 ^= KW[0][2]; + C3 ^= KW[0][3]; + + for (r = 1; r < ROUNDS - 1;) + { + r0 = mcol((S[C0&255]&255) ^ ((S[(C1>>8)&255]&255)<<8) ^ ((S[(C2>>16)&255]&255)<<16) ^ (S[(C3>>24)&255]<<24)) ^ KW[r][0]; + r1 = mcol((S[C1&255]&255) ^ ((S[(C2>>8)&255]&255)<<8) ^ ((S[(C3>>16)&255]&255)<<16) ^ (S[(C0>>24)&255]<<24)) ^ KW[r][1]; + r2 = mcol((S[C2&255]&255) ^ ((S[(C3>>8)&255]&255)<<8) ^ ((S[(C0>>16)&255]&255)<<16) ^ (S[(C1>>24)&255]<<24)) ^ KW[r][2]; + r3 = mcol((S[C3&255]&255) ^ ((S[(C0>>8)&255]&255)<<8) ^ ((S[(C1>>16)&255]&255)<<16) ^ (S[(C2>>24)&255]<<24)) ^ KW[r++][3]; + C0 = mcol((S[r0&255]&255) ^ ((S[(r1>>8)&255]&255)<<8) ^ ((S[(r2>>16)&255]&255)<<16) ^ (S[(r3>>24)&255]<<24)) ^ KW[r][0]; + C1 = mcol((S[r1&255]&255) ^ ((S[(r2>>8)&255]&255)<<8) ^ ((S[(r3>>16)&255]&255)<<16) ^ (S[(r0>>24)&255]<<24)) ^ KW[r][1]; + C2 = mcol((S[r2&255]&255) ^ ((S[(r3>>8)&255]&255)<<8) ^ ((S[(r0>>16)&255]&255)<<16) ^ (S[(r1>>24)&255]<<24)) ^ KW[r][2]; + C3 = mcol((S[r3&255]&255) ^ ((S[(r0>>8)&255]&255)<<8) ^ ((S[(r1>>16)&255]&255)<<16) ^ (S[(r2>>24)&255]<<24)) ^ KW[r++][3]; + } + + r0 = mcol((S[C0&255]&255) ^ ((S[(C1>>8)&255]&255)<<8) ^ ((S[(C2>>16)&255]&255)<<16) ^ (S[(C3>>24)&255]<<24)) ^ KW[r][0]; + r1 = mcol((S[C1&255]&255) ^ ((S[(C2>>8)&255]&255)<<8) ^ ((S[(C3>>16)&255]&255)<<16) ^ (S[(C0>>24)&255]<<24)) ^ KW[r][1]; + r2 = mcol((S[C2&255]&255) ^ ((S[(C3>>8)&255]&255)<<8) ^ ((S[(C0>>16)&255]&255)<<16) ^ (S[(C1>>24)&255]<<24)) ^ KW[r][2]; + r3 = mcol((S[C3&255]&255) ^ ((S[(C0>>8)&255]&255)<<8) ^ ((S[(C1>>16)&255]&255)<<16) ^ (S[(C2>>24)&255]<<24)) ^ KW[r++][3]; + + // the final round is a simple function of S + + C0 = (S[r0&255]&255) ^ ((S[(r1>>8)&255]&255)<<8) ^ ((S[(r2>>16)&255]&255)<<16) ^ (S[(r3>>24)&255]<<24) ^ KW[r][0]; + C1 = (S[r1&255]&255) ^ ((S[(r2>>8)&255]&255)<<8) ^ ((S[(r3>>16)&255]&255)<<16) ^ (S[(r0>>24)&255]<<24) ^ KW[r][1]; + C2 = (S[r2&255]&255) ^ ((S[(r3>>8)&255]&255)<<8) ^ ((S[(r0>>16)&255]&255)<<16) ^ (S[(r1>>24)&255]<<24) ^ KW[r][2]; + C3 = (S[r3&255]&255) ^ ((S[(r0>>8)&255]&255)<<8) ^ ((S[(r1>>16)&255]&255)<<16) ^ (S[(r2>>24)&255]<<24) ^ KW[r][3]; + + } + + private void decryptBlock(int[][] KW) + { + int r, r0, r1, r2, r3; + + C0 ^= KW[ROUNDS][0]; + C1 ^= KW[ROUNDS][1]; + C2 ^= KW[ROUNDS][2]; + C3 ^= KW[ROUNDS][3]; + + for (r = ROUNDS-1; r>1;) + { + r0 = inv_mcol((Si[C0&255]&255) ^ ((Si[(C3>>8)&255]&255)<<8) ^ ((Si[(C2>>16)&255]&255)<<16) ^ (Si[(C1>>24)&255]<<24)) ^ KW[r][0]; + r1 = inv_mcol((Si[C1&255]&255) ^ ((Si[(C0>>8)&255]&255)<<8) ^ ((Si[(C3>>16)&255]&255)<<16) ^ (Si[(C2>>24)&255]<<24)) ^ KW[r][1]; + r2 = inv_mcol((Si[C2&255]&255) ^ ((Si[(C1>>8)&255]&255)<<8) ^ ((Si[(C0>>16)&255]&255)<<16) ^ (Si[(C3>>24)&255]<<24)) ^ KW[r][2]; + r3 = inv_mcol((Si[C3&255]&255) ^ ((Si[(C2>>8)&255]&255)<<8) ^ ((Si[(C1>>16)&255]&255)<<16) ^ (Si[(C0>>24)&255]<<24)) ^ KW[r--][3]; + C0 = inv_mcol((Si[r0&255]&255) ^ ((Si[(r3>>8)&255]&255)<<8) ^ ((Si[(r2>>16)&255]&255)<<16) ^ (Si[(r1>>24)&255]<<24)) ^ KW[r][0]; + C1 = inv_mcol((Si[r1&255]&255) ^ ((Si[(r0>>8)&255]&255)<<8) ^ ((Si[(r3>>16)&255]&255)<<16) ^ (Si[(r2>>24)&255]<<24)) ^ KW[r][1]; + C2 = inv_mcol((Si[r2&255]&255) ^ ((Si[(r1>>8)&255]&255)<<8) ^ ((Si[(r0>>16)&255]&255)<<16) ^ (Si[(r3>>24)&255]<<24)) ^ KW[r][2]; + C3 = inv_mcol((Si[r3&255]&255) ^ ((Si[(r2>>8)&255]&255)<<8) ^ ((Si[(r1>>16)&255]&255)<<16) ^ (Si[(r0>>24)&255]<<24)) ^ KW[r--][3]; + } + + r0 = inv_mcol((Si[C0&255]&255) ^ ((Si[(C3>>8)&255]&255)<<8) ^ ((Si[(C2>>16)&255]&255)<<16) ^ (Si[(C1>>24)&255]<<24)) ^ KW[r][0]; + r1 = inv_mcol((Si[C1&255]&255) ^ ((Si[(C0>>8)&255]&255)<<8) ^ ((Si[(C3>>16)&255]&255)<<16) ^ (Si[(C2>>24)&255]<<24)) ^ KW[r][1]; + r2 = inv_mcol((Si[C2&255]&255) ^ ((Si[(C1>>8)&255]&255)<<8) ^ ((Si[(C0>>16)&255]&255)<<16) ^ (Si[(C3>>24)&255]<<24)) ^ KW[r][2]; + r3 = inv_mcol((Si[C3&255]&255) ^ ((Si[(C2>>8)&255]&255)<<8) ^ ((Si[(C1>>16)&255]&255)<<16) ^ (Si[(C0>>24)&255]<<24)) ^ KW[r][3]; + + // the final round's table is a simple function of Si + + C0 = (Si[r0&255]&255) ^ ((Si[(r3>>8)&255]&255)<<8) ^ ((Si[(r2>>16)&255]&255)<<16) ^ (Si[(r1>>24)&255]<<24) ^ KW[0][0]; + C1 = (Si[r1&255]&255) ^ ((Si[(r0>>8)&255]&255)<<8) ^ ((Si[(r3>>16)&255]&255)<<16) ^ (Si[(r2>>24)&255]<<24) ^ KW[0][1]; + C2 = (Si[r2&255]&255) ^ ((Si[(r1>>8)&255]&255)<<8) ^ ((Si[(r0>>16)&255]&255)<<16) ^ (Si[(r3>>24)&255]<<24) ^ KW[0][2]; + C3 = (Si[r3&255]&255) ^ ((Si[(r2>>8)&255]&255)<<8) ^ ((Si[(r1>>16)&255]&255)<<16) ^ (Si[(r0>>24)&255]<<24) ^ KW[0][3]; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/AESWrapEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/AESWrapEngine.java new file mode 100644 index 000000000..5dc18b54c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/AESWrapEngine.java @@ -0,0 +1,16 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +/** + * an implementation of the AES Key Wrapper from the NIST Key Wrap + * Specification. + *

+ * For further details see: http://csrc.nist.gov/encryption/kms/key-wrap.pdf. + */ +public class AESWrapEngine + extends RFC3394WrapEngine +{ + public AESWrapEngine() + { + super(new AESEngine()); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/BlowfishEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/BlowfishEngine.java new file mode 100644 index 000000000..0ec3f1be4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/BlowfishEngine.java @@ -0,0 +1,576 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * A class that provides Blowfish key encryption operations, + * such as encoding data and generating keys. + * All the algorithms herein are from Applied Cryptography + * and implement a simplified cryptography interface. + */ +public final class BlowfishEngine +implements BlockCipher +{ + private final static int[] + KP = { + 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, + 0xA4093822, 0x299F31D0, 0x082EFA98, 0xEC4E6C89, + 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, + 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, + 0x9216D5D9, 0x8979FB1B + }, + + KS0 = { + 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, + 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, + 0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16, + 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, + 0x0D95748F, 0x728EB658, 0x718BCD58, 0x82154AEE, + 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, + 0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF, + 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, + 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60, + 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, + 0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, + 0xA15486AF, 0x7C72E993, 0xB3EE1411, 0x636FBC2A, + 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, + 0xAFD6BA33, 0x6C24CF5C, 0x7A325381, 0x28958677, + 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, + 0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032, + 0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88, + 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239, + 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, + 0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0, + 0x6A51A0D2, 0xD8542F68, 0x960FA728, 0xAB5133A3, + 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, + 0xA1F1651D, 0x39AF0176, 0x66CA593E, 0x82430E88, + 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, + 0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6, + 0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D, + 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B, + 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, + 0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA, + 0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, 0x196A2463, + 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, + 0x6DFC511F, 0x9B30952C, 0xCC814544, 0xAF5EBD09, + 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, + 0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB, + 0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279, + 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8, + 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, + 0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82, + 0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, + 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, + 0x695B27B0, 0xBBCA58C8, 0xE1FFA35D, 0xB8F011A0, + 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, + 0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790, + 0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, + 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4, + 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, + 0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, + 0x8FF6E2FB, 0xF2122B64, 0x8888B812, 0x900DF01C, + 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, + 0x2F2F2218, 0xBE0E1777, 0xEA752DFE, 0x8B021FA1, + 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, + 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9, + 0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477, + 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF, + 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, + 0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF, + 0x2464369B, 0xF009B91E, 0x5563911D, 0x59DFA6AA, + 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, + 0x83260376, 0x6295CFA9, 0x11C81968, 0x4E734A41, + 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, + 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400, + 0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915, + 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664, + 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A + }, + + KS1 = { + 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, + 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266, + 0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1, + 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, + 0x3F54989A, 0x5B429D65, 0x6B8FE4D6, 0x99F73FD6, + 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, + 0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E, + 0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1, + 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, + 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, + 0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF, + 0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD, + 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, + 0x3AE5E581, 0x37C2DADC, 0xC8B57634, 0x9AF3DDA7, + 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, + 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331, + 0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF, + 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, + 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, + 0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87, + 0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, + 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, + 0xEF1C1847, 0x3215D908, 0xDD433B37, 0x24C2BA16, + 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, + 0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B, + 0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509, + 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, + 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, + 0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F, + 0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A, + 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, + 0xF2F74EA7, 0x361D2B3D, 0x1939260F, 0x19C27960, + 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, + 0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28, + 0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802, + 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, + 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, + 0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF, + 0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, + 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, + 0x648B1EAF, 0x19BDF0CA, 0xA02369B9, 0x655ABB50, + 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, + 0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8, + 0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281, + 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, + 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, + 0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128, + 0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, + 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, + 0x45EEE2B6, 0xA3AAABEA, 0xDB6C4F15, 0xFACB4FD0, + 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, + 0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250, + 0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3, + 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, + 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, + 0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061, + 0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB, + 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, + 0xA6078084, 0x19F8509E, 0xE8EFD855, 0x61D99735, + 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, + 0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9, + 0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340, + 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, + 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7 + }, + + KS2 = { + 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, + 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068, + 0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF, + 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, + 0x4D95FC1D, 0x96B591AF, 0x70F4DDD3, 0x66A02F45, + 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, + 0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A, + 0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB, + 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, + 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, + 0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42, + 0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, + 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, + 0x3A6EFA74, 0xDD5B4332, 0x6841E7F7, 0xCA7820FB, + 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, + 0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B, + 0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33, + 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, + 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, + 0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC, + 0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17, + 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, + 0x257B7834, 0x602A9C60, 0xDFF8E8A3, 0x1F636C1B, + 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, + 0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922, + 0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728, + 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, + 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, + 0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37, + 0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, + 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, + 0xF1290DC7, 0xCC00FFA3, 0xB5390F92, 0x690FED0B, + 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, + 0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB, + 0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D, + 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, + 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, + 0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9, + 0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, + 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, + 0x9DBC8057, 0xF0F7C086, 0x60787BF8, 0x6003604D, + 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, + 0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F, + 0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61, + 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, + 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, + 0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2, + 0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C, + 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, + 0xB77F19B6, 0xE0A9DC09, 0x662D09A1, 0xC4324633, + 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, + 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169, + 0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52, + 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, + 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, + 0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62, + 0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634, + 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, + 0x6F05E409, 0x4B7C0188, 0x39720A3D, 0x7C927C24, + 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, + 0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4, + 0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C, + 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, + 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0 + }, + + KS3 = { + 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, + 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE, + 0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, + 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, + 0x5748AB2F, 0xBC946E79, 0xC6A376D2, 0x6549C2C8, + 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, + 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304, + 0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22, + 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, + 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, + 0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9, + 0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59, + 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, + 0xE990FD5A, 0x9E34D797, 0x2CF0B7D9, 0x022B8B51, + 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, + 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C, + 0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B, + 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, + 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, + 0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD, + 0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A, + 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, + 0x7533D928, 0xB155FDF5, 0x03563482, 0x8ABA3CBB, + 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, + 0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991, + 0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32, + 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, + 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, + 0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE, + 0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, + 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, + 0x72EACEA8, 0xFA6484BB, 0x8D6612AE, 0xBF3C6F47, + 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, + 0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D, + 0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84, + 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, + 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, + 0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD, + 0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, + 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, + 0x1A908749, 0xD44FBD9A, 0xD0DADECB, 0xD50ADA38, + 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, + 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C, + 0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525, + 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, + 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, + 0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964, + 0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E, + 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, + 0xDF359F8D, 0x9B992F2E, 0xE60B6F47, 0x0FE3F11D, + 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, + 0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299, + 0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02, + 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, + 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, + 0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A, + 0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, + 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, + 0x53113EC0, 0x1640E3D3, 0x38ABBD60, 0x2547ADF0, + 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, + 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E, + 0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9, + 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, + 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6 + }; + + //==================================== + // Useful constants + //==================================== + + private static final int ROUNDS = 16; + private static final int BLOCK_SIZE = 8; // bytes = 64 bits + private static final int SBOX_SK = 256; + private static final int P_SZ = ROUNDS+2; + + private final int[] S0, S1, S2, S3; // the s-boxes + private final int[] P; // the p-array + + private boolean encrypting = false; + + private byte[] workingKey = null; + + public BlowfishEngine() + { + S0 = new int[SBOX_SK]; + S1 = new int[SBOX_SK]; + S2 = new int[SBOX_SK]; + S3 = new int[SBOX_SK]; + P = new int[P_SZ]; + } + + /** + * initialise a Blowfish cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + this.encrypting = encrypting; + this.workingKey = ((KeyParameter)params).getKey(); + setKey(this.workingKey); + + return; + } + + throw new IllegalArgumentException("invalid parameter passed to Blowfish init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "Blowfish"; + } + + public final int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("Blowfish not initialised"); + } + + if ((inOff + BLOCK_SIZE) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + BLOCK_SIZE) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (encrypting) + { + encryptBlock(in, inOff, out, outOff); + } + else + { + decryptBlock(in, inOff, out, outOff); + } + + return BLOCK_SIZE; + } + + public void reset() + { + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + //================================== + // Private Implementation + //================================== + + private int F(int x) + { + return (((S0[(x >>> 24)] + S1[(x >>> 16) & 0xff]) + ^ S2[(x >>> 8) & 0xff]) + S3[x & 0xff]); + } + + /** + * apply the encryption cycle to each value pair in the table. + */ + private void processTable( + int xl, + int xr, + int[] table) + { + int size = table.length; + + for (int s = 0; s < size; s += 2) + { + xl ^= P[0]; + + for (int i = 1; i < ROUNDS; i += 2) + { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i + 1]; + } + + xr ^= P[ROUNDS + 1]; + + table[s] = xr; + table[s + 1] = xl; + + xr = xl; // end of cycle swap + xl = table[s]; + } + } + + private void setKey(byte[] key) + { + /* + * - comments are from _Applied Crypto_, Schneier, p338 + * please be careful comparing the two, AC numbers the + * arrays from 1, the enclosed code from 0. + * + * (1) + * Initialise the S-boxes and the P-array, with a fixed string + * This string contains the hexadecimal digits of pi (3.141...) + */ + System.arraycopy(KS0, 0, S0, 0, SBOX_SK); + System.arraycopy(KS1, 0, S1, 0, SBOX_SK); + System.arraycopy(KS2, 0, S2, 0, SBOX_SK); + System.arraycopy(KS3, 0, S3, 0, SBOX_SK); + + System.arraycopy(KP, 0, P, 0, P_SZ); + + /* + * (2) + * Now, XOR P[0] with the first 32 bits of the key, XOR P[1] with the + * second 32-bits of the key, and so on for all bits of the key + * (up to P[17]). Repeatedly cycle through the key bits until the + * entire P-array has been XOR-ed with the key bits + */ + int keyLength = key.length; + int keyIndex = 0; + + for (int i=0; i < P_SZ; i++) + { + // get the 32 bits of the key, in 4 * 8 bit chunks + int data = 0x0000000; + for (int j=0; j < 4; j++) + { + // create a 32 bit block + data = (data << 8) | (key[keyIndex++] & 0xff); + + // wrap when we get to the end of the key + if (keyIndex >= keyLength) + { + keyIndex = 0; + } + } + // XOR the newly created 32 bit chunk onto the P-array + P[i] ^= data; + } + + /* + * (3) + * Encrypt the all-zero string with the Blowfish algorithm, using + * the subkeys described in (1) and (2) + * + * (4) + * Replace P1 and P2 with the output of step (3) + * + * (5) + * Encrypt the output of step(3) using the Blowfish algorithm, + * with the modified subkeys. + * + * (6) + * Replace P3 and P4 with the output of step (5) + * + * (7) + * Continue the process, replacing all elements of the P-array + * and then all four S-boxes in order, with the output of the + * continuously changing Blowfish algorithm + */ + + processTable(0, 0, P); + processTable(P[P_SZ - 2], P[P_SZ - 1], S0); + processTable(S0[SBOX_SK - 2], S0[SBOX_SK - 1], S1); + processTable(S1[SBOX_SK - 2], S1[SBOX_SK - 1], S2); + processTable(S2[SBOX_SK - 2], S2[SBOX_SK - 1], S3); + } + + /** + * Encrypt the given input starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * The input will be an exact multiple of our blocksize. + */ + private void encryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + int xl = BytesTo32bits(src, srcIndex); + int xr = BytesTo32bits(src, srcIndex+4); + + xl ^= P[0]; + + for (int i = 1; i < ROUNDS; i += 2) + { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i + 1]; + } + + xr ^= P[ROUNDS + 1]; + + Bits32ToBytes(xr, dst, dstIndex); + Bits32ToBytes(xl, dst, dstIndex + 4); + } + + /** + * Decrypt the given input starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * The input will be an exact multiple of our blocksize. + */ + private void decryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + int xl = BytesTo32bits(src, srcIndex); + int xr = BytesTo32bits(src, srcIndex + 4); + + xl ^= P[ROUNDS + 1]; + + for (int i = ROUNDS; i > 0 ; i -= 2) + { + xr ^= F(xl) ^ P[i]; + xl ^= F(xr) ^ P[i - 1]; + } + + xr ^= P[0]; + + Bits32ToBytes(xr, dst, dstIndex); + Bits32ToBytes(xl, dst, dstIndex+4); + } + + private int BytesTo32bits(byte[] b, int i) + { + return ((b[i] & 0xff) << 24) | + ((b[i+1] & 0xff) << 16) | + ((b[i+2] & 0xff) << 8) | + ((b[i+3] & 0xff)); + } + + private void Bits32ToBytes(int in, byte[] b, int offset) + { + b[offset + 3] = (byte)in; + b[offset + 2] = (byte)(in >> 8); + b[offset + 1] = (byte)(in >> 16); + b[offset] = (byte)(in >> 24); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/CAST5Engine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/CAST5Engine.java new file mode 100644 index 000000000..829385866 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/CAST5Engine.java @@ -0,0 +1,830 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * A class that provides CAST key encryption operations, + * such as encoding data and generating keys. + * + * All the algorithms herein are from the Internet RFC's + * + * RFC2144 - CAST5 (64bit block, 40-128bit key) + * RFC2612 - CAST6 (128bit block, 128-256bit key) + * + * and implement a simplified cryptography interface. + */ +public class CAST5Engine + implements BlockCipher +{ + protected final static int M32 = 0xffffffff; + + protected final static int[] + S1 = { +0x30fb40d4, 0x9fa0ff0b, 0x6beccd2f, 0x3f258c7a, 0x1e213f2f, 0x9c004dd3, 0x6003e540, 0xcf9fc949, +0xbfd4af27, 0x88bbbdb5, 0xe2034090, 0x98d09675, 0x6e63a0e0, 0x15c361d2, 0xc2e7661d, 0x22d4ff8e, +0x28683b6f, 0xc07fd059, 0xff2379c8, 0x775f50e2, 0x43c340d3, 0xdf2f8656, 0x887ca41a, 0xa2d2bd2d, +0xa1c9e0d6, 0x346c4819, 0x61b76d87, 0x22540f2f, 0x2abe32e1, 0xaa54166b, 0x22568e3a, 0xa2d341d0, +0x66db40c8, 0xa784392f, 0x004dff2f, 0x2db9d2de, 0x97943fac, 0x4a97c1d8, 0x527644b7, 0xb5f437a7, +0xb82cbaef, 0xd751d159, 0x6ff7f0ed, 0x5a097a1f, 0x827b68d0, 0x90ecf52e, 0x22b0c054, 0xbc8e5935, +0x4b6d2f7f, 0x50bb64a2, 0xd2664910, 0xbee5812d, 0xb7332290, 0xe93b159f, 0xb48ee411, 0x4bff345d, +0xfd45c240, 0xad31973f, 0xc4f6d02e, 0x55fc8165, 0xd5b1caad, 0xa1ac2dae, 0xa2d4b76d, 0xc19b0c50, +0x882240f2, 0x0c6e4f38, 0xa4e4bfd7, 0x4f5ba272, 0x564c1d2f, 0xc59c5319, 0xb949e354, 0xb04669fe, +0xb1b6ab8a, 0xc71358dd, 0x6385c545, 0x110f935d, 0x57538ad5, 0x6a390493, 0xe63d37e0, 0x2a54f6b3, +0x3a787d5f, 0x6276a0b5, 0x19a6fcdf, 0x7a42206a, 0x29f9d4d5, 0xf61b1891, 0xbb72275e, 0xaa508167, +0x38901091, 0xc6b505eb, 0x84c7cb8c, 0x2ad75a0f, 0x874a1427, 0xa2d1936b, 0x2ad286af, 0xaa56d291, +0xd7894360, 0x425c750d, 0x93b39e26, 0x187184c9, 0x6c00b32d, 0x73e2bb14, 0xa0bebc3c, 0x54623779, +0x64459eab, 0x3f328b82, 0x7718cf82, 0x59a2cea6, 0x04ee002e, 0x89fe78e6, 0x3fab0950, 0x325ff6c2, +0x81383f05, 0x6963c5c8, 0x76cb5ad6, 0xd49974c9, 0xca180dcf, 0x380782d5, 0xc7fa5cf6, 0x8ac31511, +0x35e79e13, 0x47da91d0, 0xf40f9086, 0xa7e2419e, 0x31366241, 0x051ef495, 0xaa573b04, 0x4a805d8d, +0x548300d0, 0x00322a3c, 0xbf64cddf, 0xba57a68e, 0x75c6372b, 0x50afd341, 0xa7c13275, 0x915a0bf5, +0x6b54bfab, 0x2b0b1426, 0xab4cc9d7, 0x449ccd82, 0xf7fbf265, 0xab85c5f3, 0x1b55db94, 0xaad4e324, +0xcfa4bd3f, 0x2deaa3e2, 0x9e204d02, 0xc8bd25ac, 0xeadf55b3, 0xd5bd9e98, 0xe31231b2, 0x2ad5ad6c, +0x954329de, 0xadbe4528, 0xd8710f69, 0xaa51c90f, 0xaa786bf6, 0x22513f1e, 0xaa51a79b, 0x2ad344cc, +0x7b5a41f0, 0xd37cfbad, 0x1b069505, 0x41ece491, 0xb4c332e6, 0x032268d4, 0xc9600acc, 0xce387e6d, +0xbf6bb16c, 0x6a70fb78, 0x0d03d9c9, 0xd4df39de, 0xe01063da, 0x4736f464, 0x5ad328d8, 0xb347cc96, +0x75bb0fc3, 0x98511bfb, 0x4ffbcc35, 0xb58bcf6a, 0xe11f0abc, 0xbfc5fe4a, 0xa70aec10, 0xac39570a, +0x3f04442f, 0x6188b153, 0xe0397a2e, 0x5727cb79, 0x9ceb418f, 0x1cacd68d, 0x2ad37c96, 0x0175cb9d, +0xc69dff09, 0xc75b65f0, 0xd9db40d8, 0xec0e7779, 0x4744ead4, 0xb11c3274, 0xdd24cb9e, 0x7e1c54bd, +0xf01144f9, 0xd2240eb1, 0x9675b3fd, 0xa3ac3755, 0xd47c27af, 0x51c85f4d, 0x56907596, 0xa5bb15e6, +0x580304f0, 0xca042cf1, 0x011a37ea, 0x8dbfaadb, 0x35ba3e4a, 0x3526ffa0, 0xc37b4d09, 0xbc306ed9, +0x98a52666, 0x5648f725, 0xff5e569d, 0x0ced63d0, 0x7c63b2cf, 0x700b45e1, 0xd5ea50f1, 0x85a92872, +0xaf1fbda7, 0xd4234870, 0xa7870bf3, 0x2d3b4d79, 0x42e04198, 0x0cd0ede7, 0x26470db8, 0xf881814c, +0x474d6ad7, 0x7c0c5e5c, 0xd1231959, 0x381b7298, 0xf5d2f4db, 0xab838653, 0x6e2f1e23, 0x83719c9e, +0xbd91e046, 0x9a56456e, 0xdc39200c, 0x20c8c571, 0x962bda1c, 0xe1e696ff, 0xb141ab08, 0x7cca89b9, +0x1a69e783, 0x02cc4843, 0xa2f7c579, 0x429ef47d, 0x427b169c, 0x5ac9f049, 0xdd8f0f00, 0x5c8165bf + }, + S2 = { +0x1f201094, 0xef0ba75b, 0x69e3cf7e, 0x393f4380, 0xfe61cf7a, 0xeec5207a, 0x55889c94, 0x72fc0651, +0xada7ef79, 0x4e1d7235, 0xd55a63ce, 0xde0436ba, 0x99c430ef, 0x5f0c0794, 0x18dcdb7d, 0xa1d6eff3, +0xa0b52f7b, 0x59e83605, 0xee15b094, 0xe9ffd909, 0xdc440086, 0xef944459, 0xba83ccb3, 0xe0c3cdfb, +0xd1da4181, 0x3b092ab1, 0xf997f1c1, 0xa5e6cf7b, 0x01420ddb, 0xe4e7ef5b, 0x25a1ff41, 0xe180f806, +0x1fc41080, 0x179bee7a, 0xd37ac6a9, 0xfe5830a4, 0x98de8b7f, 0x77e83f4e, 0x79929269, 0x24fa9f7b, +0xe113c85b, 0xacc40083, 0xd7503525, 0xf7ea615f, 0x62143154, 0x0d554b63, 0x5d681121, 0xc866c359, +0x3d63cf73, 0xcee234c0, 0xd4d87e87, 0x5c672b21, 0x071f6181, 0x39f7627f, 0x361e3084, 0xe4eb573b, +0x602f64a4, 0xd63acd9c, 0x1bbc4635, 0x9e81032d, 0x2701f50c, 0x99847ab4, 0xa0e3df79, 0xba6cf38c, +0x10843094, 0x2537a95e, 0xf46f6ffe, 0xa1ff3b1f, 0x208cfb6a, 0x8f458c74, 0xd9e0a227, 0x4ec73a34, +0xfc884f69, 0x3e4de8df, 0xef0e0088, 0x3559648d, 0x8a45388c, 0x1d804366, 0x721d9bfd, 0xa58684bb, +0xe8256333, 0x844e8212, 0x128d8098, 0xfed33fb4, 0xce280ae1, 0x27e19ba5, 0xd5a6c252, 0xe49754bd, +0xc5d655dd, 0xeb667064, 0x77840b4d, 0xa1b6a801, 0x84db26a9, 0xe0b56714, 0x21f043b7, 0xe5d05860, +0x54f03084, 0x066ff472, 0xa31aa153, 0xdadc4755, 0xb5625dbf, 0x68561be6, 0x83ca6b94, 0x2d6ed23b, +0xeccf01db, 0xa6d3d0ba, 0xb6803d5c, 0xaf77a709, 0x33b4a34c, 0x397bc8d6, 0x5ee22b95, 0x5f0e5304, +0x81ed6f61, 0x20e74364, 0xb45e1378, 0xde18639b, 0x881ca122, 0xb96726d1, 0x8049a7e8, 0x22b7da7b, +0x5e552d25, 0x5272d237, 0x79d2951c, 0xc60d894c, 0x488cb402, 0x1ba4fe5b, 0xa4b09f6b, 0x1ca815cf, +0xa20c3005, 0x8871df63, 0xb9de2fcb, 0x0cc6c9e9, 0x0beeff53, 0xe3214517, 0xb4542835, 0x9f63293c, +0xee41e729, 0x6e1d2d7c, 0x50045286, 0x1e6685f3, 0xf33401c6, 0x30a22c95, 0x31a70850, 0x60930f13, +0x73f98417, 0xa1269859, 0xec645c44, 0x52c877a9, 0xcdff33a6, 0xa02b1741, 0x7cbad9a2, 0x2180036f, +0x50d99c08, 0xcb3f4861, 0xc26bd765, 0x64a3f6ab, 0x80342676, 0x25a75e7b, 0xe4e6d1fc, 0x20c710e6, +0xcdf0b680, 0x17844d3b, 0x31eef84d, 0x7e0824e4, 0x2ccb49eb, 0x846a3bae, 0x8ff77888, 0xee5d60f6, +0x7af75673, 0x2fdd5cdb, 0xa11631c1, 0x30f66f43, 0xb3faec54, 0x157fd7fa, 0xef8579cc, 0xd152de58, +0xdb2ffd5e, 0x8f32ce19, 0x306af97a, 0x02f03ef8, 0x99319ad5, 0xc242fa0f, 0xa7e3ebb0, 0xc68e4906, +0xb8da230c, 0x80823028, 0xdcdef3c8, 0xd35fb171, 0x088a1bc8, 0xbec0c560, 0x61a3c9e8, 0xbca8f54d, +0xc72feffa, 0x22822e99, 0x82c570b4, 0xd8d94e89, 0x8b1c34bc, 0x301e16e6, 0x273be979, 0xb0ffeaa6, +0x61d9b8c6, 0x00b24869, 0xb7ffce3f, 0x08dc283b, 0x43daf65a, 0xf7e19798, 0x7619b72f, 0x8f1c9ba4, +0xdc8637a0, 0x16a7d3b1, 0x9fc393b7, 0xa7136eeb, 0xc6bcc63e, 0x1a513742, 0xef6828bc, 0x520365d6, +0x2d6a77ab, 0x3527ed4b, 0x821fd216, 0x095c6e2e, 0xdb92f2fb, 0x5eea29cb, 0x145892f5, 0x91584f7f, +0x5483697b, 0x2667a8cc, 0x85196048, 0x8c4bacea, 0x833860d4, 0x0d23e0f9, 0x6c387e8a, 0x0ae6d249, +0xb284600c, 0xd835731d, 0xdcb1c647, 0xac4c56ea, 0x3ebd81b3, 0x230eabb0, 0x6438bc87, 0xf0b5b1fa, +0x8f5ea2b3, 0xfc184642, 0x0a036b7a, 0x4fb089bd, 0x649da589, 0xa345415e, 0x5c038323, 0x3e5d3bb9, +0x43d79572, 0x7e6dd07c, 0x06dfdf1e, 0x6c6cc4ef, 0x7160a539, 0x73bfbe70, 0x83877605, 0x4523ecf1 + }, + S3 = { +0x8defc240, 0x25fa5d9f, 0xeb903dbf, 0xe810c907, 0x47607fff, 0x369fe44b, 0x8c1fc644, 0xaececa90, +0xbeb1f9bf, 0xeefbcaea, 0xe8cf1950, 0x51df07ae, 0x920e8806, 0xf0ad0548, 0xe13c8d83, 0x927010d5, +0x11107d9f, 0x07647db9, 0xb2e3e4d4, 0x3d4f285e, 0xb9afa820, 0xfade82e0, 0xa067268b, 0x8272792e, +0x553fb2c0, 0x489ae22b, 0xd4ef9794, 0x125e3fbc, 0x21fffcee, 0x825b1bfd, 0x9255c5ed, 0x1257a240, +0x4e1a8302, 0xbae07fff, 0x528246e7, 0x8e57140e, 0x3373f7bf, 0x8c9f8188, 0xa6fc4ee8, 0xc982b5a5, +0xa8c01db7, 0x579fc264, 0x67094f31, 0xf2bd3f5f, 0x40fff7c1, 0x1fb78dfc, 0x8e6bd2c1, 0x437be59b, +0x99b03dbf, 0xb5dbc64b, 0x638dc0e6, 0x55819d99, 0xa197c81c, 0x4a012d6e, 0xc5884a28, 0xccc36f71, +0xb843c213, 0x6c0743f1, 0x8309893c, 0x0feddd5f, 0x2f7fe850, 0xd7c07f7e, 0x02507fbf, 0x5afb9a04, +0xa747d2d0, 0x1651192e, 0xaf70bf3e, 0x58c31380, 0x5f98302e, 0x727cc3c4, 0x0a0fb402, 0x0f7fef82, +0x8c96fdad, 0x5d2c2aae, 0x8ee99a49, 0x50da88b8, 0x8427f4a0, 0x1eac5790, 0x796fb449, 0x8252dc15, +0xefbd7d9b, 0xa672597d, 0xada840d8, 0x45f54504, 0xfa5d7403, 0xe83ec305, 0x4f91751a, 0x925669c2, +0x23efe941, 0xa903f12e, 0x60270df2, 0x0276e4b6, 0x94fd6574, 0x927985b2, 0x8276dbcb, 0x02778176, +0xf8af918d, 0x4e48f79e, 0x8f616ddf, 0xe29d840e, 0x842f7d83, 0x340ce5c8, 0x96bbb682, 0x93b4b148, +0xef303cab, 0x984faf28, 0x779faf9b, 0x92dc560d, 0x224d1e20, 0x8437aa88, 0x7d29dc96, 0x2756d3dc, +0x8b907cee, 0xb51fd240, 0xe7c07ce3, 0xe566b4a1, 0xc3e9615e, 0x3cf8209d, 0x6094d1e3, 0xcd9ca341, +0x5c76460e, 0x00ea983b, 0xd4d67881, 0xfd47572c, 0xf76cedd9, 0xbda8229c, 0x127dadaa, 0x438a074e, +0x1f97c090, 0x081bdb8a, 0x93a07ebe, 0xb938ca15, 0x97b03cff, 0x3dc2c0f8, 0x8d1ab2ec, 0x64380e51, +0x68cc7bfb, 0xd90f2788, 0x12490181, 0x5de5ffd4, 0xdd7ef86a, 0x76a2e214, 0xb9a40368, 0x925d958f, +0x4b39fffa, 0xba39aee9, 0xa4ffd30b, 0xfaf7933b, 0x6d498623, 0x193cbcfa, 0x27627545, 0x825cf47a, +0x61bd8ba0, 0xd11e42d1, 0xcead04f4, 0x127ea392, 0x10428db7, 0x8272a972, 0x9270c4a8, 0x127de50b, +0x285ba1c8, 0x3c62f44f, 0x35c0eaa5, 0xe805d231, 0x428929fb, 0xb4fcdf82, 0x4fb66a53, 0x0e7dc15b, +0x1f081fab, 0x108618ae, 0xfcfd086d, 0xf9ff2889, 0x694bcc11, 0x236a5cae, 0x12deca4d, 0x2c3f8cc5, +0xd2d02dfe, 0xf8ef5896, 0xe4cf52da, 0x95155b67, 0x494a488c, 0xb9b6a80c, 0x5c8f82bc, 0x89d36b45, +0x3a609437, 0xec00c9a9, 0x44715253, 0x0a874b49, 0xd773bc40, 0x7c34671c, 0x02717ef6, 0x4feb5536, +0xa2d02fff, 0xd2bf60c4, 0xd43f03c0, 0x50b4ef6d, 0x07478cd1, 0x006e1888, 0xa2e53f55, 0xb9e6d4bc, +0xa2048016, 0x97573833, 0xd7207d67, 0xde0f8f3d, 0x72f87b33, 0xabcc4f33, 0x7688c55d, 0x7b00a6b0, +0x947b0001, 0x570075d2, 0xf9bb88f8, 0x8942019e, 0x4264a5ff, 0x856302e0, 0x72dbd92b, 0xee971b69, +0x6ea22fde, 0x5f08ae2b, 0xaf7a616d, 0xe5c98767, 0xcf1febd2, 0x61efc8c2, 0xf1ac2571, 0xcc8239c2, +0x67214cb8, 0xb1e583d1, 0xb7dc3e62, 0x7f10bdce, 0xf90a5c38, 0x0ff0443d, 0x606e6dc6, 0x60543a49, +0x5727c148, 0x2be98a1d, 0x8ab41738, 0x20e1be24, 0xaf96da0f, 0x68458425, 0x99833be5, 0x600d457d, +0x282f9350, 0x8334b362, 0xd91d1120, 0x2b6d8da0, 0x642b1e31, 0x9c305a00, 0x52bce688, 0x1b03588a, +0xf7baefd5, 0x4142ed9c, 0xa4315c11, 0x83323ec5, 0xdfef4636, 0xa133c501, 0xe9d3531c, 0xee353783 + }, + S4 = { +0x9db30420, 0x1fb6e9de, 0xa7be7bef, 0xd273a298, 0x4a4f7bdb, 0x64ad8c57, 0x85510443, 0xfa020ed1, +0x7e287aff, 0xe60fb663, 0x095f35a1, 0x79ebf120, 0xfd059d43, 0x6497b7b1, 0xf3641f63, 0x241e4adf, +0x28147f5f, 0x4fa2b8cd, 0xc9430040, 0x0cc32220, 0xfdd30b30, 0xc0a5374f, 0x1d2d00d9, 0x24147b15, +0xee4d111a, 0x0fca5167, 0x71ff904c, 0x2d195ffe, 0x1a05645f, 0x0c13fefe, 0x081b08ca, 0x05170121, +0x80530100, 0xe83e5efe, 0xac9af4f8, 0x7fe72701, 0xd2b8ee5f, 0x06df4261, 0xbb9e9b8a, 0x7293ea25, +0xce84ffdf, 0xf5718801, 0x3dd64b04, 0xa26f263b, 0x7ed48400, 0x547eebe6, 0x446d4ca0, 0x6cf3d6f5, +0x2649abdf, 0xaea0c7f5, 0x36338cc1, 0x503f7e93, 0xd3772061, 0x11b638e1, 0x72500e03, 0xf80eb2bb, +0xabe0502e, 0xec8d77de, 0x57971e81, 0xe14f6746, 0xc9335400, 0x6920318f, 0x081dbb99, 0xffc304a5, +0x4d351805, 0x7f3d5ce3, 0xa6c866c6, 0x5d5bcca9, 0xdaec6fea, 0x9f926f91, 0x9f46222f, 0x3991467d, +0xa5bf6d8e, 0x1143c44f, 0x43958302, 0xd0214eeb, 0x022083b8, 0x3fb6180c, 0x18f8931e, 0x281658e6, +0x26486e3e, 0x8bd78a70, 0x7477e4c1, 0xb506e07c, 0xf32d0a25, 0x79098b02, 0xe4eabb81, 0x28123b23, +0x69dead38, 0x1574ca16, 0xdf871b62, 0x211c40b7, 0xa51a9ef9, 0x0014377b, 0x041e8ac8, 0x09114003, +0xbd59e4d2, 0xe3d156d5, 0x4fe876d5, 0x2f91a340, 0x557be8de, 0x00eae4a7, 0x0ce5c2ec, 0x4db4bba6, +0xe756bdff, 0xdd3369ac, 0xec17b035, 0x06572327, 0x99afc8b0, 0x56c8c391, 0x6b65811c, 0x5e146119, +0x6e85cb75, 0xbe07c002, 0xc2325577, 0x893ff4ec, 0x5bbfc92d, 0xd0ec3b25, 0xb7801ab7, 0x8d6d3b24, +0x20c763ef, 0xc366a5fc, 0x9c382880, 0x0ace3205, 0xaac9548a, 0xeca1d7c7, 0x041afa32, 0x1d16625a, +0x6701902c, 0x9b757a54, 0x31d477f7, 0x9126b031, 0x36cc6fdb, 0xc70b8b46, 0xd9e66a48, 0x56e55a79, +0x026a4ceb, 0x52437eff, 0x2f8f76b4, 0x0df980a5, 0x8674cde3, 0xedda04eb, 0x17a9be04, 0x2c18f4df, +0xb7747f9d, 0xab2af7b4, 0xefc34d20, 0x2e096b7c, 0x1741a254, 0xe5b6a035, 0x213d42f6, 0x2c1c7c26, +0x61c2f50f, 0x6552daf9, 0xd2c231f8, 0x25130f69, 0xd8167fa2, 0x0418f2c8, 0x001a96a6, 0x0d1526ab, +0x63315c21, 0x5e0a72ec, 0x49bafefd, 0x187908d9, 0x8d0dbd86, 0x311170a7, 0x3e9b640c, 0xcc3e10d7, +0xd5cad3b6, 0x0caec388, 0xf73001e1, 0x6c728aff, 0x71eae2a1, 0x1f9af36e, 0xcfcbd12f, 0xc1de8417, +0xac07be6b, 0xcb44a1d8, 0x8b9b0f56, 0x013988c3, 0xb1c52fca, 0xb4be31cd, 0xd8782806, 0x12a3a4e2, +0x6f7de532, 0x58fd7eb6, 0xd01ee900, 0x24adffc2, 0xf4990fc5, 0x9711aac5, 0x001d7b95, 0x82e5e7d2, +0x109873f6, 0x00613096, 0xc32d9521, 0xada121ff, 0x29908415, 0x7fbb977f, 0xaf9eb3db, 0x29c9ed2a, +0x5ce2a465, 0xa730f32c, 0xd0aa3fe8, 0x8a5cc091, 0xd49e2ce7, 0x0ce454a9, 0xd60acd86, 0x015f1919, +0x77079103, 0xdea03af6, 0x78a8565e, 0xdee356df, 0x21f05cbe, 0x8b75e387, 0xb3c50651, 0xb8a5c3ef, +0xd8eeb6d2, 0xe523be77, 0xc2154529, 0x2f69efdf, 0xafe67afb, 0xf470c4b2, 0xf3e0eb5b, 0xd6cc9876, +0x39e4460c, 0x1fda8538, 0x1987832f, 0xca007367, 0xa99144f8, 0x296b299e, 0x492fc295, 0x9266beab, +0xb5676e69, 0x9bd3ddda, 0xdf7e052f, 0xdb25701c, 0x1b5e51ee, 0xf65324e6, 0x6afce36c, 0x0316cc04, +0x8644213e, 0xb7dc59d0, 0x7965291f, 0xccd6fd43, 0x41823979, 0x932bcdf6, 0xb657c34d, 0x4edfd282, +0x7ae5290c, 0x3cb9536b, 0x851e20fe, 0x9833557e, 0x13ecf0b0, 0xd3ffb372, 0x3f85c5c1, 0x0aef7ed2 + }, + S5 = { +0x7ec90c04, 0x2c6e74b9, 0x9b0e66df, 0xa6337911, 0xb86a7fff, 0x1dd358f5, 0x44dd9d44, 0x1731167f, +0x08fbf1fa, 0xe7f511cc, 0xd2051b00, 0x735aba00, 0x2ab722d8, 0x386381cb, 0xacf6243a, 0x69befd7a, +0xe6a2e77f, 0xf0c720cd, 0xc4494816, 0xccf5c180, 0x38851640, 0x15b0a848, 0xe68b18cb, 0x4caadeff, +0x5f480a01, 0x0412b2aa, 0x259814fc, 0x41d0efe2, 0x4e40b48d, 0x248eb6fb, 0x8dba1cfe, 0x41a99b02, +0x1a550a04, 0xba8f65cb, 0x7251f4e7, 0x95a51725, 0xc106ecd7, 0x97a5980a, 0xc539b9aa, 0x4d79fe6a, +0xf2f3f763, 0x68af8040, 0xed0c9e56, 0x11b4958b, 0xe1eb5a88, 0x8709e6b0, 0xd7e07156, 0x4e29fea7, +0x6366e52d, 0x02d1c000, 0xc4ac8e05, 0x9377f571, 0x0c05372a, 0x578535f2, 0x2261be02, 0xd642a0c9, +0xdf13a280, 0x74b55bd2, 0x682199c0, 0xd421e5ec, 0x53fb3ce8, 0xc8adedb3, 0x28a87fc9, 0x3d959981, +0x5c1ff900, 0xfe38d399, 0x0c4eff0b, 0x062407ea, 0xaa2f4fb1, 0x4fb96976, 0x90c79505, 0xb0a8a774, +0xef55a1ff, 0xe59ca2c2, 0xa6b62d27, 0xe66a4263, 0xdf65001f, 0x0ec50966, 0xdfdd55bc, 0x29de0655, +0x911e739a, 0x17af8975, 0x32c7911c, 0x89f89468, 0x0d01e980, 0x524755f4, 0x03b63cc9, 0x0cc844b2, +0xbcf3f0aa, 0x87ac36e9, 0xe53a7426, 0x01b3d82b, 0x1a9e7449, 0x64ee2d7e, 0xcddbb1da, 0x01c94910, +0xb868bf80, 0x0d26f3fd, 0x9342ede7, 0x04a5c284, 0x636737b6, 0x50f5b616, 0xf24766e3, 0x8eca36c1, +0x136e05db, 0xfef18391, 0xfb887a37, 0xd6e7f7d4, 0xc7fb7dc9, 0x3063fcdf, 0xb6f589de, 0xec2941da, +0x26e46695, 0xb7566419, 0xf654efc5, 0xd08d58b7, 0x48925401, 0xc1bacb7f, 0xe5ff550f, 0xb6083049, +0x5bb5d0e8, 0x87d72e5a, 0xab6a6ee1, 0x223a66ce, 0xc62bf3cd, 0x9e0885f9, 0x68cb3e47, 0x086c010f, +0xa21de820, 0xd18b69de, 0xf3f65777, 0xfa02c3f6, 0x407edac3, 0xcbb3d550, 0x1793084d, 0xb0d70eba, +0x0ab378d5, 0xd951fb0c, 0xded7da56, 0x4124bbe4, 0x94ca0b56, 0x0f5755d1, 0xe0e1e56e, 0x6184b5be, +0x580a249f, 0x94f74bc0, 0xe327888e, 0x9f7b5561, 0xc3dc0280, 0x05687715, 0x646c6bd7, 0x44904db3, +0x66b4f0a3, 0xc0f1648a, 0x697ed5af, 0x49e92ff6, 0x309e374f, 0x2cb6356a, 0x85808573, 0x4991f840, +0x76f0ae02, 0x083be84d, 0x28421c9a, 0x44489406, 0x736e4cb8, 0xc1092910, 0x8bc95fc6, 0x7d869cf4, +0x134f616f, 0x2e77118d, 0xb31b2be1, 0xaa90b472, 0x3ca5d717, 0x7d161bba, 0x9cad9010, 0xaf462ba2, +0x9fe459d2, 0x45d34559, 0xd9f2da13, 0xdbc65487, 0xf3e4f94e, 0x176d486f, 0x097c13ea, 0x631da5c7, +0x445f7382, 0x175683f4, 0xcdc66a97, 0x70be0288, 0xb3cdcf72, 0x6e5dd2f3, 0x20936079, 0x459b80a5, +0xbe60e2db, 0xa9c23101, 0xeba5315c, 0x224e42f2, 0x1c5c1572, 0xf6721b2c, 0x1ad2fff3, 0x8c25404e, +0x324ed72f, 0x4067b7fd, 0x0523138e, 0x5ca3bc78, 0xdc0fd66e, 0x75922283, 0x784d6b17, 0x58ebb16e, +0x44094f85, 0x3f481d87, 0xfcfeae7b, 0x77b5ff76, 0x8c2302bf, 0xaaf47556, 0x5f46b02a, 0x2b092801, +0x3d38f5f7, 0x0ca81f36, 0x52af4a8a, 0x66d5e7c0, 0xdf3b0874, 0x95055110, 0x1b5ad7a8, 0xf61ed5ad, +0x6cf6e479, 0x20758184, 0xd0cefa65, 0x88f7be58, 0x4a046826, 0x0ff6f8f3, 0xa09c7f70, 0x5346aba0, +0x5ce96c28, 0xe176eda3, 0x6bac307f, 0x376829d2, 0x85360fa9, 0x17e3fe2a, 0x24b79767, 0xf5a96b20, +0xd6cd2595, 0x68ff1ebf, 0x7555442c, 0xf19f06be, 0xf9e0659a, 0xeeb9491d, 0x34010718, 0xbb30cab8, +0xe822fe15, 0x88570983, 0x750e6249, 0xda627e55, 0x5e76ffa8, 0xb1534546, 0x6d47de08, 0xefe9e7d4 + }, + S6 = { +0xf6fa8f9d, 0x2cac6ce1, 0x4ca34867, 0xe2337f7c, 0x95db08e7, 0x016843b4, 0xeced5cbc, 0x325553ac, +0xbf9f0960, 0xdfa1e2ed, 0x83f0579d, 0x63ed86b9, 0x1ab6a6b8, 0xde5ebe39, 0xf38ff732, 0x8989b138, +0x33f14961, 0xc01937bd, 0xf506c6da, 0xe4625e7e, 0xa308ea99, 0x4e23e33c, 0x79cbd7cc, 0x48a14367, +0xa3149619, 0xfec94bd5, 0xa114174a, 0xeaa01866, 0xa084db2d, 0x09a8486f, 0xa888614a, 0x2900af98, +0x01665991, 0xe1992863, 0xc8f30c60, 0x2e78ef3c, 0xd0d51932, 0xcf0fec14, 0xf7ca07d2, 0xd0a82072, +0xfd41197e, 0x9305a6b0, 0xe86be3da, 0x74bed3cd, 0x372da53c, 0x4c7f4448, 0xdab5d440, 0x6dba0ec3, +0x083919a7, 0x9fbaeed9, 0x49dbcfb0, 0x4e670c53, 0x5c3d9c01, 0x64bdb941, 0x2c0e636a, 0xba7dd9cd, +0xea6f7388, 0xe70bc762, 0x35f29adb, 0x5c4cdd8d, 0xf0d48d8c, 0xb88153e2, 0x08a19866, 0x1ae2eac8, +0x284caf89, 0xaa928223, 0x9334be53, 0x3b3a21bf, 0x16434be3, 0x9aea3906, 0xefe8c36e, 0xf890cdd9, +0x80226dae, 0xc340a4a3, 0xdf7e9c09, 0xa694a807, 0x5b7c5ecc, 0x221db3a6, 0x9a69a02f, 0x68818a54, +0xceb2296f, 0x53c0843a, 0xfe893655, 0x25bfe68a, 0xb4628abc, 0xcf222ebf, 0x25ac6f48, 0xa9a99387, +0x53bddb65, 0xe76ffbe7, 0xe967fd78, 0x0ba93563, 0x8e342bc1, 0xe8a11be9, 0x4980740d, 0xc8087dfc, +0x8de4bf99, 0xa11101a0, 0x7fd37975, 0xda5a26c0, 0xe81f994f, 0x9528cd89, 0xfd339fed, 0xb87834bf, +0x5f04456d, 0x22258698, 0xc9c4c83b, 0x2dc156be, 0x4f628daa, 0x57f55ec5, 0xe2220abe, 0xd2916ebf, +0x4ec75b95, 0x24f2c3c0, 0x42d15d99, 0xcd0d7fa0, 0x7b6e27ff, 0xa8dc8af0, 0x7345c106, 0xf41e232f, +0x35162386, 0xe6ea8926, 0x3333b094, 0x157ec6f2, 0x372b74af, 0x692573e4, 0xe9a9d848, 0xf3160289, +0x3a62ef1d, 0xa787e238, 0xf3a5f676, 0x74364853, 0x20951063, 0x4576698d, 0xb6fad407, 0x592af950, +0x36f73523, 0x4cfb6e87, 0x7da4cec0, 0x6c152daa, 0xcb0396a8, 0xc50dfe5d, 0xfcd707ab, 0x0921c42f, +0x89dff0bb, 0x5fe2be78, 0x448f4f33, 0x754613c9, 0x2b05d08d, 0x48b9d585, 0xdc049441, 0xc8098f9b, +0x7dede786, 0xc39a3373, 0x42410005, 0x6a091751, 0x0ef3c8a6, 0x890072d6, 0x28207682, 0xa9a9f7be, +0xbf32679d, 0xd45b5b75, 0xb353fd00, 0xcbb0e358, 0x830f220a, 0x1f8fb214, 0xd372cf08, 0xcc3c4a13, +0x8cf63166, 0x061c87be, 0x88c98f88, 0x6062e397, 0x47cf8e7a, 0xb6c85283, 0x3cc2acfb, 0x3fc06976, +0x4e8f0252, 0x64d8314d, 0xda3870e3, 0x1e665459, 0xc10908f0, 0x513021a5, 0x6c5b68b7, 0x822f8aa0, +0x3007cd3e, 0x74719eef, 0xdc872681, 0x073340d4, 0x7e432fd9, 0x0c5ec241, 0x8809286c, 0xf592d891, +0x08a930f6, 0x957ef305, 0xb7fbffbd, 0xc266e96f, 0x6fe4ac98, 0xb173ecc0, 0xbc60b42a, 0x953498da, +0xfba1ae12, 0x2d4bd736, 0x0f25faab, 0xa4f3fceb, 0xe2969123, 0x257f0c3d, 0x9348af49, 0x361400bc, +0xe8816f4a, 0x3814f200, 0xa3f94043, 0x9c7a54c2, 0xbc704f57, 0xda41e7f9, 0xc25ad33a, 0x54f4a084, +0xb17f5505, 0x59357cbe, 0xedbd15c8, 0x7f97c5ab, 0xba5ac7b5, 0xb6f6deaf, 0x3a479c3a, 0x5302da25, +0x653d7e6a, 0x54268d49, 0x51a477ea, 0x5017d55b, 0xd7d25d88, 0x44136c76, 0x0404a8c8, 0xb8e5a121, +0xb81a928a, 0x60ed5869, 0x97c55b96, 0xeaec991b, 0x29935913, 0x01fdb7f1, 0x088e8dfa, 0x9ab6f6f5, +0x3b4cbf9f, 0x4a5de3ab, 0xe6051d35, 0xa0e1d855, 0xd36b4cf1, 0xf544edeb, 0xb0e93524, 0xbebb8fbd, +0xa2d762cf, 0x49c92f54, 0x38b5f331, 0x7128a454, 0x48392905, 0xa65b1db8, 0x851c97bd, 0xd675cf2f + }, + S7 = { +0x85e04019, 0x332bf567, 0x662dbfff, 0xcfc65693, 0x2a8d7f6f, 0xab9bc912, 0xde6008a1, 0x2028da1f, +0x0227bce7, 0x4d642916, 0x18fac300, 0x50f18b82, 0x2cb2cb11, 0xb232e75c, 0x4b3695f2, 0xb28707de, +0xa05fbcf6, 0xcd4181e9, 0xe150210c, 0xe24ef1bd, 0xb168c381, 0xfde4e789, 0x5c79b0d8, 0x1e8bfd43, +0x4d495001, 0x38be4341, 0x913cee1d, 0x92a79c3f, 0x089766be, 0xbaeeadf4, 0x1286becf, 0xb6eacb19, +0x2660c200, 0x7565bde4, 0x64241f7a, 0x8248dca9, 0xc3b3ad66, 0x28136086, 0x0bd8dfa8, 0x356d1cf2, +0x107789be, 0xb3b2e9ce, 0x0502aa8f, 0x0bc0351e, 0x166bf52a, 0xeb12ff82, 0xe3486911, 0xd34d7516, +0x4e7b3aff, 0x5f43671b, 0x9cf6e037, 0x4981ac83, 0x334266ce, 0x8c9341b7, 0xd0d854c0, 0xcb3a6c88, +0x47bc2829, 0x4725ba37, 0xa66ad22b, 0x7ad61f1e, 0x0c5cbafa, 0x4437f107, 0xb6e79962, 0x42d2d816, +0x0a961288, 0xe1a5c06e, 0x13749e67, 0x72fc081a, 0xb1d139f7, 0xf9583745, 0xcf19df58, 0xbec3f756, +0xc06eba30, 0x07211b24, 0x45c28829, 0xc95e317f, 0xbc8ec511, 0x38bc46e9, 0xc6e6fa14, 0xbae8584a, +0xad4ebc46, 0x468f508b, 0x7829435f, 0xf124183b, 0x821dba9f, 0xaff60ff4, 0xea2c4e6d, 0x16e39264, +0x92544a8b, 0x009b4fc3, 0xaba68ced, 0x9ac96f78, 0x06a5b79a, 0xb2856e6e, 0x1aec3ca9, 0xbe838688, +0x0e0804e9, 0x55f1be56, 0xe7e5363b, 0xb3a1f25d, 0xf7debb85, 0x61fe033c, 0x16746233, 0x3c034c28, +0xda6d0c74, 0x79aac56c, 0x3ce4e1ad, 0x51f0c802, 0x98f8f35a, 0x1626a49f, 0xeed82b29, 0x1d382fe3, +0x0c4fb99a, 0xbb325778, 0x3ec6d97b, 0x6e77a6a9, 0xcb658b5c, 0xd45230c7, 0x2bd1408b, 0x60c03eb7, +0xb9068d78, 0xa33754f4, 0xf430c87d, 0xc8a71302, 0xb96d8c32, 0xebd4e7be, 0xbe8b9d2d, 0x7979fb06, +0xe7225308, 0x8b75cf77, 0x11ef8da4, 0xe083c858, 0x8d6b786f, 0x5a6317a6, 0xfa5cf7a0, 0x5dda0033, +0xf28ebfb0, 0xf5b9c310, 0xa0eac280, 0x08b9767a, 0xa3d9d2b0, 0x79d34217, 0x021a718d, 0x9ac6336a, +0x2711fd60, 0x438050e3, 0x069908a8, 0x3d7fedc4, 0x826d2bef, 0x4eeb8476, 0x488dcf25, 0x36c9d566, +0x28e74e41, 0xc2610aca, 0x3d49a9cf, 0xbae3b9df, 0xb65f8de6, 0x92aeaf64, 0x3ac7d5e6, 0x9ea80509, +0xf22b017d, 0xa4173f70, 0xdd1e16c3, 0x15e0d7f9, 0x50b1b887, 0x2b9f4fd5, 0x625aba82, 0x6a017962, +0x2ec01b9c, 0x15488aa9, 0xd716e740, 0x40055a2c, 0x93d29a22, 0xe32dbf9a, 0x058745b9, 0x3453dc1e, +0xd699296e, 0x496cff6f, 0x1c9f4986, 0xdfe2ed07, 0xb87242d1, 0x19de7eae, 0x053e561a, 0x15ad6f8c, +0x66626c1c, 0x7154c24c, 0xea082b2a, 0x93eb2939, 0x17dcb0f0, 0x58d4f2ae, 0x9ea294fb, 0x52cf564c, +0x9883fe66, 0x2ec40581, 0x763953c3, 0x01d6692e, 0xd3a0c108, 0xa1e7160e, 0xe4f2dfa6, 0x693ed285, +0x74904698, 0x4c2b0edd, 0x4f757656, 0x5d393378, 0xa132234f, 0x3d321c5d, 0xc3f5e194, 0x4b269301, +0xc79f022f, 0x3c997e7e, 0x5e4f9504, 0x3ffafbbd, 0x76f7ad0e, 0x296693f4, 0x3d1fce6f, 0xc61e45be, +0xd3b5ab34, 0xf72bf9b7, 0x1b0434c0, 0x4e72b567, 0x5592a33d, 0xb5229301, 0xcfd2a87f, 0x60aeb767, +0x1814386b, 0x30bcc33d, 0x38a0c07d, 0xfd1606f2, 0xc363519b, 0x589dd390, 0x5479f8e6, 0x1cb8d647, +0x97fd61a9, 0xea7759f4, 0x2d57539d, 0x569a58cf, 0xe84e63ad, 0x462e1b78, 0x6580f87e, 0xf3817914, +0x91da55f4, 0x40a230f3, 0xd1988f35, 0xb6e318d2, 0x3ffa50bc, 0x3d40f021, 0xc3c0bdae, 0x4958c24c, +0x518f36b2, 0x84b1d370, 0x0fedce83, 0x878ddada, 0xf2a279c7, 0x94e01be8, 0x90716f4b, 0x954b8aa3 + }, + S8 = { +0xe216300d, 0xbbddfffc, 0xa7ebdabd, 0x35648095, 0x7789f8b7, 0xe6c1121b, 0x0e241600, 0x052ce8b5, +0x11a9cfb0, 0xe5952f11, 0xece7990a, 0x9386d174, 0x2a42931c, 0x76e38111, 0xb12def3a, 0x37ddddfc, +0xde9adeb1, 0x0a0cc32c, 0xbe197029, 0x84a00940, 0xbb243a0f, 0xb4d137cf, 0xb44e79f0, 0x049eedfd, +0x0b15a15d, 0x480d3168, 0x8bbbde5a, 0x669ded42, 0xc7ece831, 0x3f8f95e7, 0x72df191b, 0x7580330d, +0x94074251, 0x5c7dcdfa, 0xabbe6d63, 0xaa402164, 0xb301d40a, 0x02e7d1ca, 0x53571dae, 0x7a3182a2, +0x12a8ddec, 0xfdaa335d, 0x176f43e8, 0x71fb46d4, 0x38129022, 0xce949ad4, 0xb84769ad, 0x965bd862, +0x82f3d055, 0x66fb9767, 0x15b80b4e, 0x1d5b47a0, 0x4cfde06f, 0xc28ec4b8, 0x57e8726e, 0x647a78fc, +0x99865d44, 0x608bd593, 0x6c200e03, 0x39dc5ff6, 0x5d0b00a3, 0xae63aff2, 0x7e8bd632, 0x70108c0c, +0xbbd35049, 0x2998df04, 0x980cf42a, 0x9b6df491, 0x9e7edd53, 0x06918548, 0x58cb7e07, 0x3b74ef2e, +0x522fffb1, 0xd24708cc, 0x1c7e27cd, 0xa4eb215b, 0x3cf1d2e2, 0x19b47a38, 0x424f7618, 0x35856039, +0x9d17dee7, 0x27eb35e6, 0xc9aff67b, 0x36baf5b8, 0x09c467cd, 0xc18910b1, 0xe11dbf7b, 0x06cd1af8, +0x7170c608, 0x2d5e3354, 0xd4de495a, 0x64c6d006, 0xbcc0c62c, 0x3dd00db3, 0x708f8f34, 0x77d51b42, +0x264f620f, 0x24b8d2bf, 0x15c1b79e, 0x46a52564, 0xf8d7e54e, 0x3e378160, 0x7895cda5, 0x859c15a5, +0xe6459788, 0xc37bc75f, 0xdb07ba0c, 0x0676a3ab, 0x7f229b1e, 0x31842e7b, 0x24259fd7, 0xf8bef472, +0x835ffcb8, 0x6df4c1f2, 0x96f5b195, 0xfd0af0fc, 0xb0fe134c, 0xe2506d3d, 0x4f9b12ea, 0xf215f225, +0xa223736f, 0x9fb4c428, 0x25d04979, 0x34c713f8, 0xc4618187, 0xea7a6e98, 0x7cd16efc, 0x1436876c, +0xf1544107, 0xbedeee14, 0x56e9af27, 0xa04aa441, 0x3cf7c899, 0x92ecbae6, 0xdd67016d, 0x151682eb, +0xa842eedf, 0xfdba60b4, 0xf1907b75, 0x20e3030f, 0x24d8c29e, 0xe139673b, 0xefa63fb8, 0x71873054, +0xb6f2cf3b, 0x9f326442, 0xcb15a4cc, 0xb01a4504, 0xf1e47d8d, 0x844a1be5, 0xbae7dfdc, 0x42cbda70, +0xcd7dae0a, 0x57e85b7a, 0xd53f5af6, 0x20cf4d8c, 0xcea4d428, 0x79d130a4, 0x3486ebfb, 0x33d3cddc, +0x77853b53, 0x37effcb5, 0xc5068778, 0xe580b3e6, 0x4e68b8f4, 0xc5c8b37e, 0x0d809ea2, 0x398feb7c, +0x132a4f94, 0x43b7950e, 0x2fee7d1c, 0x223613bd, 0xdd06caa2, 0x37df932b, 0xc4248289, 0xacf3ebc3, +0x5715f6b7, 0xef3478dd, 0xf267616f, 0xc148cbe4, 0x9052815e, 0x5e410fab, 0xb48a2465, 0x2eda7fa4, +0xe87b40e4, 0xe98ea084, 0x5889e9e1, 0xefd390fc, 0xdd07d35b, 0xdb485694, 0x38d7e5b2, 0x57720101, +0x730edebc, 0x5b643113, 0x94917e4f, 0x503c2fba, 0x646f1282, 0x7523d24a, 0xe0779695, 0xf9c17a8f, +0x7a5b2121, 0xd187b896, 0x29263a4d, 0xba510cdf, 0x81f47c9f, 0xad1163ed, 0xea7b5965, 0x1a00726e, +0x11403092, 0x00da6d77, 0x4a0cdd61, 0xad1f4603, 0x605bdfb0, 0x9eedc364, 0x22ebe6a8, 0xcee7d28a, +0xa0e736a0, 0x5564a6b9, 0x10853209, 0xc7eb8f37, 0x2de705ca, 0x8951570f, 0xdf09822b, 0xbd691a6c, +0xaa12e4f2, 0x87451c0f, 0xe0f6a27a, 0x3ada4819, 0x4cf1764f, 0x0d771c2b, 0x67cdb156, 0x350d8384, +0x5938fa0f, 0x42399ef3, 0x36997b07, 0x0e84093d, 0x4aa93e61, 0x8360d87b, 0x1fa98b0c, 0x1149382c, +0xe97625a5, 0x0614d1b7, 0x0e25244b, 0x0c768347, 0x589e8d82, 0x0d2059d1, 0xa466bb1e, 0xf8da0a82, +0x04f19130, 0xba6e4ec0, 0x99265164, 0x1ee7230d, 0x50b2ad80, 0xeaee6801, 0x8db2a283, 0xea8bf59e + }; + + //==================================== + // Useful constants + //==================================== + + protected static final int MAX_ROUNDS = 16; + protected static final int RED_ROUNDS = 12; + + protected static final int BLOCK_SIZE = 8; // bytes = 64 bits + + protected int _Kr[] = new int[17]; // the rotating round key + protected int _Km[] = new int[17]; // the masking round key + + private boolean _encrypting = false; + + private byte[] _workingKey = null; + private int _rounds = MAX_ROUNDS; + + public CAST5Engine() + { + } + + /** + * initialise a CAST cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + _encrypting = encrypting; + _workingKey = ((KeyParameter)params).getKey(); + + setKey(_workingKey); + + return; + } + + throw new IllegalArgumentException("Invalid parameter passed to "+getAlgorithmName()+" init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "CAST5"; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (_workingKey == null) + { + throw new IllegalStateException(getAlgorithmName()+" not initialised"); + } + + int blockSize = getBlockSize(); + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("Input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("Output buffer too short"); + } + + if (_encrypting) + { + return encryptBlock(in, inOff, out, outOff); + } + else + { + return decryptBlock(in, inOff, out, outOff); + } + } + + public void reset() + { + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + //================================== + // Private Implementation + //================================== + + /* + * Creates the subkeys using the same nomenclature + * as described in RFC2144. + * + * See section 2.4 + */ + protected void setKey(byte[] key) + { + /* + * Determine the key size here, if required + * + * if keysize <= 80bits, use 12 rounds instead of 16 + * if keysize < 128bits, pad with 0 + * + * Typical key sizes => 40, 64, 80, 128 + */ + + if (key.length < 11) + { + _rounds = RED_ROUNDS; + } + + int z[] = new int[16]; + int x[] = new int[16]; + + int z03, z47, z8B, zCF; + int x03, x47, x8B, xCF; + + /* copy the key into x */ + for (int i=0; i< key.length; i++) + { + x[i] = key[i] & 0xff; + } + + /* + * This will look different because the selection of + * bytes from the input key I've already chosen the + * correct int. + */ + x03 = IntsTo32bits(x, 0x0); + x47 = IntsTo32bits(x, 0x4); + x8B = IntsTo32bits(x, 0x8); + xCF = IntsTo32bits(x, 0xC); + + z03 = x03 ^S5[x[0xD]] ^S6[x[0xF]] ^S7[x[0xC]] ^S8[x[0xE]] ^S7[x[0x8]]; + + Bits32ToInts(z03, z, 0x0); + z47 = x8B ^S5[z[0x0]] ^S6[z[0x2]] ^S7[z[0x1]] ^S8[z[0x3]] ^S8[x[0xA]]; + Bits32ToInts(z47, z, 0x4); + z8B = xCF ^S5[z[0x7]] ^S6[z[0x6]] ^S7[z[0x5]] ^S8[z[0x4]] ^S5[x[0x9]]; + Bits32ToInts(z8B, z, 0x8); + zCF = x47 ^S5[z[0xA]] ^S6[z[0x9]] ^S7[z[0xB]] ^S8[z[0x8]] ^S6[x[0xB]]; + Bits32ToInts(zCF, z, 0xC); + _Km[ 1]= S5[z[0x8]] ^ S6[z[0x9]] ^ S7[z[0x7]] ^ S8[z[0x6]] ^ S5[z[0x2]]; + _Km[ 2]= S5[z[0xA]] ^ S6[z[0xB]] ^ S7[z[0x5]] ^ S8[z[0x4]] ^ S6[z[0x6]]; + _Km[ 3]= S5[z[0xC]] ^ S6[z[0xD]] ^ S7[z[0x3]] ^ S8[z[0x2]] ^ S7[z[0x9]]; + _Km[ 4]= S5[z[0xE]] ^ S6[z[0xF]] ^ S7[z[0x1]] ^ S8[z[0x0]] ^ S8[z[0xC]]; + + z03 = IntsTo32bits(z, 0x0); + z47 = IntsTo32bits(z, 0x4); + z8B = IntsTo32bits(z, 0x8); + zCF = IntsTo32bits(z, 0xC); + x03 = z8B ^S5[z[0x5]] ^S6[z[0x7]] ^S7[z[0x4]] ^S8[z[0x6]] ^S7[z[0x0]]; + Bits32ToInts(x03, x, 0x0); + x47 = z03 ^S5[x[0x0]] ^S6[x[0x2]] ^S7[x[0x1]] ^S8[x[0x3]] ^S8[z[0x2]]; + Bits32ToInts(x47, x, 0x4); + x8B = z47 ^S5[x[0x7]] ^S6[x[0x6]] ^S7[x[0x5]] ^S8[x[0x4]] ^S5[z[0x1]]; + Bits32ToInts(x8B, x, 0x8); + xCF = zCF ^S5[x[0xA]] ^S6[x[0x9]] ^S7[x[0xB]] ^S8[x[0x8]] ^S6[z[0x3]]; + Bits32ToInts(xCF, x, 0xC); + _Km[ 5]= S5[x[0x3]] ^ S6[x[0x2]] ^ S7[x[0xC]] ^ S8[x[0xD]] ^ S5[x[0x8]]; + _Km[ 6]= S5[x[0x1]] ^ S6[x[0x0]] ^ S7[x[0xE]] ^ S8[x[0xF]] ^ S6[x[0xD]]; + _Km[ 7]= S5[x[0x7]] ^ S6[x[0x6]] ^ S7[x[0x8]] ^ S8[x[0x9]] ^ S7[x[0x3]]; + _Km[ 8]= S5[x[0x5]] ^ S6[x[0x4]] ^ S7[x[0xA]] ^ S8[x[0xB]] ^ S8[x[0x7]]; + + x03 = IntsTo32bits(x, 0x0); + x47 = IntsTo32bits(x, 0x4); + x8B = IntsTo32bits(x, 0x8); + xCF = IntsTo32bits(x, 0xC); + z03 = x03 ^S5[x[0xD]] ^S6[x[0xF]] ^S7[x[0xC]] ^S8[x[0xE]] ^S7[x[0x8]]; + Bits32ToInts(z03, z, 0x0); + z47 = x8B ^S5[z[0x0]] ^S6[z[0x2]] ^S7[z[0x1]] ^S8[z[0x3]] ^S8[x[0xA]]; + Bits32ToInts(z47, z, 0x4); + z8B = xCF ^S5[z[0x7]] ^S6[z[0x6]] ^S7[z[0x5]] ^S8[z[0x4]] ^S5[x[0x9]]; + Bits32ToInts(z8B, z, 0x8); + zCF = x47 ^S5[z[0xA]] ^S6[z[0x9]] ^S7[z[0xB]] ^S8[z[0x8]] ^S6[x[0xB]]; + Bits32ToInts(zCF, z, 0xC); + _Km[ 9]= S5[z[0x3]] ^ S6[z[0x2]] ^ S7[z[0xC]] ^ S8[z[0xD]] ^ S5[z[0x9]]; + _Km[10]= S5[z[0x1]] ^ S6[z[0x0]] ^ S7[z[0xE]] ^ S8[z[0xF]] ^ S6[z[0xc]]; + _Km[11]= S5[z[0x7]] ^ S6[z[0x6]] ^ S7[z[0x8]] ^ S8[z[0x9]] ^ S7[z[0x2]]; + _Km[12]= S5[z[0x5]] ^ S6[z[0x4]] ^ S7[z[0xA]] ^ S8[z[0xB]] ^ S8[z[0x6]]; + + z03 = IntsTo32bits(z, 0x0); + z47 = IntsTo32bits(z, 0x4); + z8B = IntsTo32bits(z, 0x8); + zCF = IntsTo32bits(z, 0xC); + x03 = z8B ^S5[z[0x5]] ^S6[z[0x7]] ^S7[z[0x4]] ^S8[z[0x6]] ^S7[z[0x0]]; + Bits32ToInts(x03, x, 0x0); + x47 = z03 ^S5[x[0x0]] ^S6[x[0x2]] ^S7[x[0x1]] ^S8[x[0x3]] ^S8[z[0x2]]; + Bits32ToInts(x47, x, 0x4); + x8B = z47 ^S5[x[0x7]] ^S6[x[0x6]] ^S7[x[0x5]] ^S8[x[0x4]] ^S5[z[0x1]]; + Bits32ToInts(x8B, x, 0x8); + xCF = zCF ^S5[x[0xA]] ^S6[x[0x9]] ^S7[x[0xB]] ^S8[x[0x8]] ^S6[z[0x3]]; + Bits32ToInts(xCF, x, 0xC); + _Km[13]= S5[x[0x8]] ^ S6[x[0x9]] ^ S7[x[0x7]] ^ S8[x[0x6]] ^ S5[x[0x3]]; + _Km[14]= S5[x[0xA]] ^ S6[x[0xB]] ^ S7[x[0x5]] ^ S8[x[0x4]] ^ S6[x[0x7]]; + _Km[15]= S5[x[0xC]] ^ S6[x[0xD]] ^ S7[x[0x3]] ^ S8[x[0x2]] ^ S7[x[0x8]]; + _Km[16]= S5[x[0xE]] ^ S6[x[0xF]] ^ S7[x[0x1]] ^ S8[x[0x0]] ^ S8[x[0xD]]; + + x03 = IntsTo32bits(x, 0x0); + x47 = IntsTo32bits(x, 0x4); + x8B = IntsTo32bits(x, 0x8); + xCF = IntsTo32bits(x, 0xC); + z03 = x03 ^S5[x[0xD]] ^S6[x[0xF]] ^S7[x[0xC]] ^S8[x[0xE]] ^S7[x[0x8]]; + Bits32ToInts(z03, z, 0x0); + z47 = x8B ^S5[z[0x0]] ^S6[z[0x2]] ^S7[z[0x1]] ^S8[z[0x3]] ^S8[x[0xA]]; + Bits32ToInts(z47, z, 0x4); + z8B = xCF ^S5[z[0x7]] ^S6[z[0x6]] ^S7[z[0x5]] ^S8[z[0x4]] ^S5[x[0x9]]; + Bits32ToInts(z8B, z, 0x8); + zCF = x47 ^S5[z[0xA]] ^S6[z[0x9]] ^S7[z[0xB]] ^S8[z[0x8]] ^S6[x[0xB]]; + Bits32ToInts(zCF, z, 0xC); + _Kr[ 1]=(S5[z[0x8]]^S6[z[0x9]]^S7[z[0x7]]^S8[z[0x6]] ^ S5[z[0x2]])&0x1f; + _Kr[ 2]=(S5[z[0xA]]^S6[z[0xB]]^S7[z[0x5]]^S8[z[0x4]] ^ S6[z[0x6]])&0x1f; + _Kr[ 3]=(S5[z[0xC]]^S6[z[0xD]]^S7[z[0x3]]^S8[z[0x2]] ^ S7[z[0x9]])&0x1f; + _Kr[ 4]=(S5[z[0xE]]^S6[z[0xF]]^S7[z[0x1]]^S8[z[0x0]] ^ S8[z[0xC]])&0x1f; + + z03 = IntsTo32bits(z, 0x0); + z47 = IntsTo32bits(z, 0x4); + z8B = IntsTo32bits(z, 0x8); + zCF = IntsTo32bits(z, 0xC); + x03 = z8B ^S5[z[0x5]] ^S6[z[0x7]] ^S7[z[0x4]] ^S8[z[0x6]] ^S7[z[0x0]]; + Bits32ToInts(x03, x, 0x0); + x47 = z03 ^S5[x[0x0]] ^S6[x[0x2]] ^S7[x[0x1]] ^S8[x[0x3]] ^S8[z[0x2]]; + Bits32ToInts(x47, x, 0x4); + x8B = z47 ^S5[x[0x7]] ^S6[x[0x6]] ^S7[x[0x5]] ^S8[x[0x4]] ^S5[z[0x1]]; + Bits32ToInts(x8B, x, 0x8); + xCF = zCF ^S5[x[0xA]] ^S6[x[0x9]] ^S7[x[0xB]] ^S8[x[0x8]] ^S6[z[0x3]]; + Bits32ToInts(xCF, x, 0xC); + _Kr[ 5]=(S5[x[0x3]]^S6[x[0x2]]^S7[x[0xC]]^S8[x[0xD]]^S5[x[0x8]])&0x1f; + _Kr[ 6]=(S5[x[0x1]]^S6[x[0x0]]^S7[x[0xE]]^S8[x[0xF]]^S6[x[0xD]])&0x1f; + _Kr[ 7]=(S5[x[0x7]]^S6[x[0x6]]^S7[x[0x8]]^S8[x[0x9]]^S7[x[0x3]])&0x1f; + _Kr[ 8]=(S5[x[0x5]]^S6[x[0x4]]^S7[x[0xA]]^S8[x[0xB]]^S8[x[0x7]])&0x1f; + + x03 = IntsTo32bits(x, 0x0); + x47 = IntsTo32bits(x, 0x4); + x8B = IntsTo32bits(x, 0x8); + xCF = IntsTo32bits(x, 0xC); + z03 = x03 ^S5[x[0xD]] ^S6[x[0xF]] ^S7[x[0xC]] ^S8[x[0xE]] ^S7[x[0x8]]; + Bits32ToInts(z03, z, 0x0); + z47 = x8B ^S5[z[0x0]] ^S6[z[0x2]] ^S7[z[0x1]] ^S8[z[0x3]] ^S8[x[0xA]]; + Bits32ToInts(z47, z, 0x4); + z8B = xCF ^S5[z[0x7]] ^S6[z[0x6]] ^S7[z[0x5]] ^S8[z[0x4]] ^S5[x[0x9]]; + Bits32ToInts(z8B, z, 0x8); + zCF = x47 ^S5[z[0xA]] ^S6[z[0x9]] ^S7[z[0xB]] ^S8[z[0x8]] ^S6[x[0xB]]; + Bits32ToInts(zCF, z, 0xC); + _Kr[ 9]=(S5[z[0x3]]^S6[z[0x2]]^S7[z[0xC]]^S8[z[0xD]]^S5[z[0x9]])&0x1f; + _Kr[10]=(S5[z[0x1]]^S6[z[0x0]]^S7[z[0xE]]^S8[z[0xF]]^S6[z[0xc]])&0x1f; + _Kr[11]=(S5[z[0x7]]^S6[z[0x6]]^S7[z[0x8]]^S8[z[0x9]]^S7[z[0x2]])&0x1f; + _Kr[12]=(S5[z[0x5]]^S6[z[0x4]]^S7[z[0xA]]^S8[z[0xB]]^S8[z[0x6]])&0x1f; + + z03 = IntsTo32bits(z, 0x0); + z47 = IntsTo32bits(z, 0x4); + z8B = IntsTo32bits(z, 0x8); + zCF = IntsTo32bits(z, 0xC); + x03 = z8B ^S5[z[0x5]] ^S6[z[0x7]] ^S7[z[0x4]] ^S8[z[0x6]] ^S7[z[0x0]]; + Bits32ToInts(x03, x, 0x0); + x47 = z03 ^S5[x[0x0]] ^S6[x[0x2]] ^S7[x[0x1]] ^S8[x[0x3]] ^S8[z[0x2]]; + Bits32ToInts(x47, x, 0x4); + x8B = z47 ^S5[x[0x7]] ^S6[x[0x6]] ^S7[x[0x5]] ^S8[x[0x4]] ^S5[z[0x1]]; + Bits32ToInts(x8B, x, 0x8); + xCF = zCF ^S5[x[0xA]] ^S6[x[0x9]] ^S7[x[0xB]] ^S8[x[0x8]] ^S6[z[0x3]]; + Bits32ToInts(xCF, x, 0xC); + _Kr[13]=(S5[x[0x8]]^S6[x[0x9]]^S7[x[0x7]]^S8[x[0x6]]^S5[x[0x3]])&0x1f; + _Kr[14]=(S5[x[0xA]]^S6[x[0xB]]^S7[x[0x5]]^S8[x[0x4]]^S6[x[0x7]])&0x1f; + _Kr[15]=(S5[x[0xC]]^S6[x[0xD]]^S7[x[0x3]]^S8[x[0x2]]^S7[x[0x8]])&0x1f; + _Kr[16]=(S5[x[0xE]]^S6[x[0xF]]^S7[x[0x1]]^S8[x[0x0]]^S8[x[0xD]])&0x1f; + } + + /** + * Encrypt the given input starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * + * @param src The plaintext buffer + * @param srcIndex An offset into src + * @param dst The ciphertext buffer + * @param dstIndex An offset into dst + */ + protected int encryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + + int result[] = new int[2]; + + // process the input block + // batch the units up into a 32 bit chunk and go for it + // the array is in bytes, the increment is 8x8 bits = 64 + + int L0 = BytesTo32bits(src, srcIndex); + int R0 = BytesTo32bits(src, srcIndex + 4); + + CAST_Encipher(L0, R0, result); + + // now stuff them into the destination block + Bits32ToBytes(result[0], dst, dstIndex); + Bits32ToBytes(result[1], dst, dstIndex + 4); + + return BLOCK_SIZE; + } + + /** + * Decrypt the given input starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * + * @param src The plaintext buffer + * @param srcIndex An offset into src + * @param dst The ciphertext buffer + * @param dstIndex An offset into dst + */ + protected int decryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + int result[] = new int[2]; + + // process the input block + // batch the units up into a 32 bit chunk and go for it + // the array is in bytes, the increment is 8x8 bits = 64 + int L16 = BytesTo32bits(src, srcIndex); + int R16 = BytesTo32bits(src, srcIndex+4); + + CAST_Decipher(L16, R16, result); + + // now stuff them into the destination block + Bits32ToBytes(result[0], dst, dstIndex); + Bits32ToBytes(result[1], dst, dstIndex+4); + + return BLOCK_SIZE; + } + + /** + * The first of the three processing functions for the + * encryption and decryption. + * + * @param D the input to be processed + * @param Kmi the mask to be used from Km[n] + * @param Kri the rotation value to be used + * + */ + protected final int F1(int D, int Kmi, int Kri) + { + int I = Kmi + D; + I = I << Kri | I >>> (32-Kri); + return ((S1[(I>>>24)&0xff]^S2[(I>>>16)&0xff])-S3[(I>>> 8)&0xff])+ + S4[I & 0xff]; + } + + /** + * The second of the three processing functions for the + * encryption and decryption. + * + * @param D the input to be processed + * @param Kmi the mask to be used from Km[n] + * @param Kri the rotation value to be used + * + */ + protected final int F2(int D, int Kmi, int Kri) + { + int I = Kmi ^ D; + I = I << Kri | I >>> (32-Kri); + return ((S1[(I>>>24)&0xff]-S2[(I>>>16)&0xff])+S3[(I>>> 8)&0xff])^ + S4[I & 0xff]; + } + + /** + * The third of the three processing functions for the + * encryption and decryption. + * + * @param D the input to be processed + * @param Kmi the mask to be used from Km[n] + * @param Kri the rotation value to be used + * + */ + protected final int F3(int D, int Kmi, int Kri) + { + int I = Kmi - D; + I = I << Kri | I >>> (32-Kri); + return ((S1[(I>>>24)&0xff]+S2[(I>>>16)&0xff])^S3[(I>>> 8)&0xff])- + S4[I & 0xff]; + } + + /** + * Does the 16 rounds to encrypt the block. + * + * @param L0 the LH-32bits of the plaintext block + * @param R0 the RH-32bits of the plaintext block + */ + protected final void CAST_Encipher(int L0, int R0, int result[]) + { + int Lp = L0; // the previous value, equiv to L[i-1] + int Rp = R0; // equivalent to R[i-1] + + /* + * numbering consistent with paper to make + * checking and validating easier + */ + int Li = L0, Ri = R0; + + for (int i = 1; i<=_rounds ; i++) + { + Lp = Li; + Rp = Ri; + + Li = Rp; + switch (i) + { + case 1: + case 4: + case 7: + case 10: + case 13: + case 16: + Ri = Lp ^ F1(Rp, _Km[i], _Kr[i]); + break; + case 2: + case 5: + case 8: + case 11: + case 14: + Ri = Lp ^ F2(Rp, _Km[i], _Kr[i]); + break; + case 3: + case 6: + case 9: + case 12: + case 15: + Ri = Lp ^ F3(Rp, _Km[i], _Kr[i]); + break; + } + } + + result[0] = Ri; + result[1] = Li; + + return; + } + + protected final void CAST_Decipher(int L16, int R16, int result[]) + { + int Lp = L16; // the previous value, equiv to L[i-1] + int Rp = R16; // equivalent to R[i-1] + + /* + * numbering consistent with paper to make + * checking and validating easier + */ + int Li = L16, Ri = R16; + + for (int i = _rounds; i > 0; i--) + { + Lp = Li; + Rp = Ri; + + Li = Rp; + switch (i) + { + case 1: + case 4: + case 7: + case 10: + case 13: + case 16: + Ri = Lp ^ F1(Rp, _Km[i], _Kr[i]); + break; + case 2: + case 5: + case 8: + case 11: + case 14: + Ri = Lp ^ F2(Rp, _Km[i], _Kr[i]); + break; + case 3: + case 6: + case 9: + case 12: + case 15: + Ri = Lp ^ F3(Rp, _Km[i], _Kr[i]); + break; + } + } + + result[0] = Ri; + result[1] = Li; + + return; + } + + protected final void Bits32ToInts(int in, int[] b, int offset) + { + b[offset + 3] = (in & 0xff); + b[offset + 2] = ((in >>> 8) & 0xff); + b[offset + 1] = ((in >>> 16) & 0xff); + b[offset] = ((in >>> 24) & 0xff); + } + + protected final int IntsTo32bits(int[] b, int i) + { + int rv = 0; + + rv = ((b[i] & 0xff) << 24) | + ((b[i+1] & 0xff) << 16) | + ((b[i+2] & 0xff) << 8) | + ((b[i+3] & 0xff)); + + return rv; + } + + protected final void Bits32ToBytes(int in, byte[] b, int offset) + { + b[offset + 3] = (byte)in; + b[offset + 2] = (byte)(in >>> 8); + b[offset + 1] = (byte)(in >>> 16); + b[offset] = (byte)(in >>> 24); + } + + protected final int BytesTo32bits(byte[] b, int i) + { + return ((b[i] & 0xff) << 24) | + ((b[i+1] & 0xff) << 16) | + ((b[i+2] & 0xff) << 8) | + ((b[i+3] & 0xff)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/CAST6Engine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/CAST6Engine.java new file mode 100644 index 000000000..c1c05a9c7 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/CAST6Engine.java @@ -0,0 +1,296 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + + +/** + * A class that provides CAST6 key encryption operations, + * such as encoding data and generating keys. + * + * All the algorithms herein are from the Internet RFC + * + * RFC2612 - CAST6 (128bit block, 128-256bit key) + * + * and implement a simplified cryptography interface. + */ +public final class CAST6Engine extends CAST5Engine +{ + //==================================== + // Useful constants + //==================================== + + protected static final int ROUNDS = 12; + + protected static final int BLOCK_SIZE = 16; // bytes = 128 bits + + /* + * Put the round and mask keys into an array. + * Kr0[i] => _Kr[i*4 + 0] + */ + protected int _Kr[] = new int[ROUNDS*4]; // the rotating round key(s) + protected int _Km[] = new int[ROUNDS*4]; // the masking round key(s) + + /* + * Key setup + */ + protected int _Tr[] = new int[24 * 8]; + protected int _Tm[] = new int[24 * 8]; + + private int[] _workingKey = new int[8]; + + public CAST6Engine() + { + } + + public String getAlgorithmName() + { + return "CAST6"; + } + + public void reset() + { + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + //================================== + // Private Implementation + //================================== + + /* + * Creates the subkeys using the same nomenclature + * as described in RFC2612. + * + * See section 2.4 + */ + protected void setKey(byte[] key) + { + int Cm = 0x5a827999; + int Mm = 0x6ed9eba1; + int Cr = 19; + int Mr = 17; + + /* + * Determine the key size here, if required + * + * if keysize < 256 bytes, pad with 0 + * + * Typical key sizes => 128, 160, 192, 224, 256 + */ + for (int i=0; i< 24; i++) + { + for (int j=0; j< 8; j++) + { + _Tm[i*8 + j] = Cm; + Cm = (Cm + Mm); // mod 2^32; + + _Tr[i*8 + j] = Cr; + Cr = (Cr + Mr) & 0x1f; // mod 32 + } + } + + byte[] tmpKey = new byte[64]; + int length = key.length; + System.arraycopy(key, 0, tmpKey, 0, length); + + // now create ABCDEFGH + for (int i=0; i< 8; i++) + { + _workingKey[i] = BytesTo32bits(tmpKey, i*4); + } + + // Generate the key schedule + for (int i=0; i< 12; i++) + { + // KAPPA <- W2i(KAPPA) + int i2 = i*2 *8; + _workingKey[6] ^= F1(_workingKey[7], _Tm[i2 ], _Tr[i2 ]); + _workingKey[5] ^= F2(_workingKey[6], _Tm[i2+1], _Tr[i2+1]); + _workingKey[4] ^= F3(_workingKey[5], _Tm[i2+2], _Tr[i2+2]); + _workingKey[3] ^= F1(_workingKey[4], _Tm[i2+3], _Tr[i2+3]); + _workingKey[2] ^= F2(_workingKey[3], _Tm[i2+4], _Tr[i2+4]); + _workingKey[1] ^= F3(_workingKey[2], _Tm[i2+5], _Tr[i2+5]); + _workingKey[0] ^= F1(_workingKey[1], _Tm[i2+6], _Tr[i2+6]); + _workingKey[7] ^= F2(_workingKey[0], _Tm[i2+7], _Tr[i2+7]); + + // KAPPA <- W2i+1(KAPPA) + i2 = (i*2 + 1)*8; + _workingKey[6] ^= F1(_workingKey[7], _Tm[i2 ], _Tr[i2 ]); + _workingKey[5] ^= F2(_workingKey[6], _Tm[i2+1], _Tr[i2+1]); + _workingKey[4] ^= F3(_workingKey[5], _Tm[i2+2], _Tr[i2+2]); + _workingKey[3] ^= F1(_workingKey[4], _Tm[i2+3], _Tr[i2+3]); + _workingKey[2] ^= F2(_workingKey[3], _Tm[i2+4], _Tr[i2+4]); + _workingKey[1] ^= F3(_workingKey[2], _Tm[i2+5], _Tr[i2+5]); + _workingKey[0] ^= F1(_workingKey[1], _Tm[i2+6], _Tr[i2+6]); + _workingKey[7] ^= F2(_workingKey[0], _Tm[i2+7], _Tr[i2+7]); + + // Kr_(i) <- KAPPA + _Kr[i*4 ] = _workingKey[0] & 0x1f; + _Kr[i*4 + 1] = _workingKey[2] & 0x1f; + _Kr[i*4 + 2] = _workingKey[4] & 0x1f; + _Kr[i*4 + 3] = _workingKey[6] & 0x1f; + + + // Km_(i) <- KAPPA + _Km[i*4 ] = _workingKey[7]; + _Km[i*4 + 1] = _workingKey[5]; + _Km[i*4 + 2] = _workingKey[3]; + _Km[i*4 + 3] = _workingKey[1]; + } + + } + + /** + * Encrypt the given input starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * + * @param src The plaintext buffer + * @param srcIndex An offset into src + * @param dst The ciphertext buffer + * @param dstIndex An offset into dst + */ + protected int encryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + + int result[] = new int[4]; + + // process the input block + // batch the units up into 4x32 bit chunks and go for it + + int A = BytesTo32bits(src, srcIndex); + int B = BytesTo32bits(src, srcIndex + 4); + int C = BytesTo32bits(src, srcIndex + 8); + int D = BytesTo32bits(src, srcIndex + 12); + + CAST_Encipher(A, B, C, D, result); + + // now stuff them into the destination block + Bits32ToBytes(result[0], dst, dstIndex); + Bits32ToBytes(result[1], dst, dstIndex + 4); + Bits32ToBytes(result[2], dst, dstIndex + 8); + Bits32ToBytes(result[3], dst, dstIndex + 12); + + return BLOCK_SIZE; + } + + /** + * Decrypt the given input starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * + * @param src The plaintext buffer + * @param srcIndex An offset into src + * @param dst The ciphertext buffer + * @param dstIndex An offset into dst + */ + protected int decryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + int result[] = new int[4]; + + // process the input block + // batch the units up into 4x32 bit chunks and go for it + int A = BytesTo32bits(src, srcIndex); + int B = BytesTo32bits(src, srcIndex + 4); + int C = BytesTo32bits(src, srcIndex + 8); + int D = BytesTo32bits(src, srcIndex + 12); + + CAST_Decipher(A, B, C, D, result); + + // now stuff them into the destination block + Bits32ToBytes(result[0], dst, dstIndex); + Bits32ToBytes(result[1], dst, dstIndex + 4); + Bits32ToBytes(result[2], dst, dstIndex + 8); + Bits32ToBytes(result[3], dst, dstIndex + 12); + + return BLOCK_SIZE; + } + + /** + * Does the 12 quad rounds rounds to encrypt the block. + * + * @param A the 00-31 bits of the plaintext block + * @param B the 32-63 bits of the plaintext block + * @param C the 64-95 bits of the plaintext block + * @param D the 96-127 bits of the plaintext block + * @param result the resulting ciphertext + */ + protected final void CAST_Encipher(int A, int B, int C, int D,int result[]) + { + int x; + for (int i=0; i< 6; i++) + { + x = i*4; + // BETA <- Qi(BETA) + C ^= F1(D, _Km[x], _Kr[x]); + B ^= F2(C, _Km[x + 1], _Kr[x + 1]); + A ^= F3(B, _Km[x + 2], _Kr[x + 2]); + D ^= F1(A, _Km[x + 3], _Kr[x + 3]); + + } + + for (int i=6; i<12; i++) + { + x = i*4; + // BETA <- QBARi(BETA) + D ^= F1(A, _Km[x + 3], _Kr[x + 3]); + A ^= F3(B, _Km[x + 2], _Kr[x + 2]); + B ^= F2(C, _Km[x + 1], _Kr[x + 1]); + C ^= F1(D, _Km[x], _Kr[x]); + + } + + result[0] = A; + result[1] = B; + result[2] = C; + result[3] = D; + } + + /** + * Does the 12 quad rounds rounds to decrypt the block. + * + * @param A the 00-31 bits of the ciphertext block + * @param B the 32-63 bits of the ciphertext block + * @param C the 64-95 bits of the ciphertext block + * @param D the 96-127 bits of the ciphertext block + * @param result the resulting plaintext + */ + protected final void CAST_Decipher(int A, int B, int C, int D,int result[]) + { + int x; + for (int i=0; i< 6; i++) + { + x = (11-i)*4; + // BETA <- Qi(BETA) + C ^= F1(D, _Km[x], _Kr[x]); + B ^= F2(C, _Km[x + 1], _Kr[x + 1]); + A ^= F3(B, _Km[x + 2], _Kr[x + 2]); + D ^= F1(A, _Km[x + 3], _Kr[x + 3]); + + } + + for (int i=6; i<12; i++) + { + x = (11-i)*4; + // BETA <- QBARi(BETA) + D ^= F1(A, _Km[x + 3], _Kr[x + 3]); + A ^= F3(B, _Km[x + 2], _Kr[x + 2]); + B ^= F2(C, _Km[x + 1], _Kr[x + 1]); + C ^= F1(D, _Km[x], _Kr[x]); + + } + + result[0] = A; + result[1] = B; + result[2] = C; + result[3] = D; + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/CamelliaEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/CamelliaEngine.java new file mode 100644 index 000000000..2de8ea778 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/CamelliaEngine.java @@ -0,0 +1,683 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * Camellia - based on RFC 3713. + */ +public class CamelliaEngine + implements BlockCipher +{ + private boolean initialised = false; + private boolean _keyIs128; + + private static final int BLOCK_SIZE = 16; + private static final int MASK8 = 0xff; + + private int[] subkey = new int[24 * 4]; + private int[] kw = new int[4 * 2]; // for whitening + private int[] ke = new int[6 * 2]; // for FL and FL^(-1) + private int[] state = new int[4]; // for encryption and decryption + + private static final int SIGMA[] = { + 0xa09e667f, 0x3bcc908b, + 0xb67ae858, 0x4caa73b2, + 0xc6ef372f, 0xe94f82be, + 0x54ff53a5, 0xf1d36f1c, + 0x10e527fa, 0xde682d1d, + 0xb05688c2, 0xb3e6c1fd + }; + + /* + * + * S-box data + * + */ + private static final int SBOX1_1110[] = { + 0x70707000, 0x82828200, 0x2c2c2c00, 0xececec00, 0xb3b3b300, 0x27272700, + 0xc0c0c000, 0xe5e5e500, 0xe4e4e400, 0x85858500, 0x57575700, 0x35353500, + 0xeaeaea00, 0x0c0c0c00, 0xaeaeae00, 0x41414100, 0x23232300, 0xefefef00, + 0x6b6b6b00, 0x93939300, 0x45454500, 0x19191900, 0xa5a5a500, 0x21212100, + 0xededed00, 0x0e0e0e00, 0x4f4f4f00, 0x4e4e4e00, 0x1d1d1d00, 0x65656500, + 0x92929200, 0xbdbdbd00, 0x86868600, 0xb8b8b800, 0xafafaf00, 0x8f8f8f00, + 0x7c7c7c00, 0xebebeb00, 0x1f1f1f00, 0xcecece00, 0x3e3e3e00, 0x30303000, + 0xdcdcdc00, 0x5f5f5f00, 0x5e5e5e00, 0xc5c5c500, 0x0b0b0b00, 0x1a1a1a00, + 0xa6a6a600, 0xe1e1e100, 0x39393900, 0xcacaca00, 0xd5d5d500, 0x47474700, + 0x5d5d5d00, 0x3d3d3d00, 0xd9d9d900, 0x01010100, 0x5a5a5a00, 0xd6d6d600, + 0x51515100, 0x56565600, 0x6c6c6c00, 0x4d4d4d00, 0x8b8b8b00, 0x0d0d0d00, + 0x9a9a9a00, 0x66666600, 0xfbfbfb00, 0xcccccc00, 0xb0b0b000, 0x2d2d2d00, + 0x74747400, 0x12121200, 0x2b2b2b00, 0x20202000, 0xf0f0f000, 0xb1b1b100, + 0x84848400, 0x99999900, 0xdfdfdf00, 0x4c4c4c00, 0xcbcbcb00, 0xc2c2c200, + 0x34343400, 0x7e7e7e00, 0x76767600, 0x05050500, 0x6d6d6d00, 0xb7b7b700, + 0xa9a9a900, 0x31313100, 0xd1d1d100, 0x17171700, 0x04040400, 0xd7d7d700, + 0x14141400, 0x58585800, 0x3a3a3a00, 0x61616100, 0xdedede00, 0x1b1b1b00, + 0x11111100, 0x1c1c1c00, 0x32323200, 0x0f0f0f00, 0x9c9c9c00, 0x16161600, + 0x53535300, 0x18181800, 0xf2f2f200, 0x22222200, 0xfefefe00, 0x44444400, + 0xcfcfcf00, 0xb2b2b200, 0xc3c3c300, 0xb5b5b500, 0x7a7a7a00, 0x91919100, + 0x24242400, 0x08080800, 0xe8e8e800, 0xa8a8a800, 0x60606000, 0xfcfcfc00, + 0x69696900, 0x50505000, 0xaaaaaa00, 0xd0d0d000, 0xa0a0a000, 0x7d7d7d00, + 0xa1a1a100, 0x89898900, 0x62626200, 0x97979700, 0x54545400, 0x5b5b5b00, + 0x1e1e1e00, 0x95959500, 0xe0e0e000, 0xffffff00, 0x64646400, 0xd2d2d200, + 0x10101000, 0xc4c4c400, 0x00000000, 0x48484800, 0xa3a3a300, 0xf7f7f700, + 0x75757500, 0xdbdbdb00, 0x8a8a8a00, 0x03030300, 0xe6e6e600, 0xdadada00, + 0x09090900, 0x3f3f3f00, 0xdddddd00, 0x94949400, 0x87878700, 0x5c5c5c00, + 0x83838300, 0x02020200, 0xcdcdcd00, 0x4a4a4a00, 0x90909000, 0x33333300, + 0x73737300, 0x67676700, 0xf6f6f600, 0xf3f3f300, 0x9d9d9d00, 0x7f7f7f00, + 0xbfbfbf00, 0xe2e2e200, 0x52525200, 0x9b9b9b00, 0xd8d8d800, 0x26262600, + 0xc8c8c800, 0x37373700, 0xc6c6c600, 0x3b3b3b00, 0x81818100, 0x96969600, + 0x6f6f6f00, 0x4b4b4b00, 0x13131300, 0xbebebe00, 0x63636300, 0x2e2e2e00, + 0xe9e9e900, 0x79797900, 0xa7a7a700, 0x8c8c8c00, 0x9f9f9f00, 0x6e6e6e00, + 0xbcbcbc00, 0x8e8e8e00, 0x29292900, 0xf5f5f500, 0xf9f9f900, 0xb6b6b600, + 0x2f2f2f00, 0xfdfdfd00, 0xb4b4b400, 0x59595900, 0x78787800, 0x98989800, + 0x06060600, 0x6a6a6a00, 0xe7e7e700, 0x46464600, 0x71717100, 0xbababa00, + 0xd4d4d400, 0x25252500, 0xababab00, 0x42424200, 0x88888800, 0xa2a2a200, + 0x8d8d8d00, 0xfafafa00, 0x72727200, 0x07070700, 0xb9b9b900, 0x55555500, + 0xf8f8f800, 0xeeeeee00, 0xacacac00, 0x0a0a0a00, 0x36363600, 0x49494900, + 0x2a2a2a00, 0x68686800, 0x3c3c3c00, 0x38383800, 0xf1f1f100, 0xa4a4a400, + 0x40404000, 0x28282800, 0xd3d3d300, 0x7b7b7b00, 0xbbbbbb00, 0xc9c9c900, + 0x43434300, 0xc1c1c100, 0x15151500, 0xe3e3e300, 0xadadad00, 0xf4f4f400, + 0x77777700, 0xc7c7c700, 0x80808000, 0x9e9e9e00 + }; + + private static final int SBOX4_4404[] = { + 0x70700070, 0x2c2c002c, 0xb3b300b3, 0xc0c000c0, 0xe4e400e4, 0x57570057, + 0xeaea00ea, 0xaeae00ae, 0x23230023, 0x6b6b006b, 0x45450045, 0xa5a500a5, + 0xeded00ed, 0x4f4f004f, 0x1d1d001d, 0x92920092, 0x86860086, 0xafaf00af, + 0x7c7c007c, 0x1f1f001f, 0x3e3e003e, 0xdcdc00dc, 0x5e5e005e, 0x0b0b000b, + 0xa6a600a6, 0x39390039, 0xd5d500d5, 0x5d5d005d, 0xd9d900d9, 0x5a5a005a, + 0x51510051, 0x6c6c006c, 0x8b8b008b, 0x9a9a009a, 0xfbfb00fb, 0xb0b000b0, + 0x74740074, 0x2b2b002b, 0xf0f000f0, 0x84840084, 0xdfdf00df, 0xcbcb00cb, + 0x34340034, 0x76760076, 0x6d6d006d, 0xa9a900a9, 0xd1d100d1, 0x04040004, + 0x14140014, 0x3a3a003a, 0xdede00de, 0x11110011, 0x32320032, 0x9c9c009c, + 0x53530053, 0xf2f200f2, 0xfefe00fe, 0xcfcf00cf, 0xc3c300c3, 0x7a7a007a, + 0x24240024, 0xe8e800e8, 0x60600060, 0x69690069, 0xaaaa00aa, 0xa0a000a0, + 0xa1a100a1, 0x62620062, 0x54540054, 0x1e1e001e, 0xe0e000e0, 0x64640064, + 0x10100010, 0x00000000, 0xa3a300a3, 0x75750075, 0x8a8a008a, 0xe6e600e6, + 0x09090009, 0xdddd00dd, 0x87870087, 0x83830083, 0xcdcd00cd, 0x90900090, + 0x73730073, 0xf6f600f6, 0x9d9d009d, 0xbfbf00bf, 0x52520052, 0xd8d800d8, + 0xc8c800c8, 0xc6c600c6, 0x81810081, 0x6f6f006f, 0x13130013, 0x63630063, + 0xe9e900e9, 0xa7a700a7, 0x9f9f009f, 0xbcbc00bc, 0x29290029, 0xf9f900f9, + 0x2f2f002f, 0xb4b400b4, 0x78780078, 0x06060006, 0xe7e700e7, 0x71710071, + 0xd4d400d4, 0xabab00ab, 0x88880088, 0x8d8d008d, 0x72720072, 0xb9b900b9, + 0xf8f800f8, 0xacac00ac, 0x36360036, 0x2a2a002a, 0x3c3c003c, 0xf1f100f1, + 0x40400040, 0xd3d300d3, 0xbbbb00bb, 0x43430043, 0x15150015, 0xadad00ad, + 0x77770077, 0x80800080, 0x82820082, 0xecec00ec, 0x27270027, 0xe5e500e5, + 0x85850085, 0x35350035, 0x0c0c000c, 0x41410041, 0xefef00ef, 0x93930093, + 0x19190019, 0x21210021, 0x0e0e000e, 0x4e4e004e, 0x65650065, 0xbdbd00bd, + 0xb8b800b8, 0x8f8f008f, 0xebeb00eb, 0xcece00ce, 0x30300030, 0x5f5f005f, + 0xc5c500c5, 0x1a1a001a, 0xe1e100e1, 0xcaca00ca, 0x47470047, 0x3d3d003d, + 0x01010001, 0xd6d600d6, 0x56560056, 0x4d4d004d, 0x0d0d000d, 0x66660066, + 0xcccc00cc, 0x2d2d002d, 0x12120012, 0x20200020, 0xb1b100b1, 0x99990099, + 0x4c4c004c, 0xc2c200c2, 0x7e7e007e, 0x05050005, 0xb7b700b7, 0x31310031, + 0x17170017, 0xd7d700d7, 0x58580058, 0x61610061, 0x1b1b001b, 0x1c1c001c, + 0x0f0f000f, 0x16160016, 0x18180018, 0x22220022, 0x44440044, 0xb2b200b2, + 0xb5b500b5, 0x91910091, 0x08080008, 0xa8a800a8, 0xfcfc00fc, 0x50500050, + 0xd0d000d0, 0x7d7d007d, 0x89890089, 0x97970097, 0x5b5b005b, 0x95950095, + 0xffff00ff, 0xd2d200d2, 0xc4c400c4, 0x48480048, 0xf7f700f7, 0xdbdb00db, + 0x03030003, 0xdada00da, 0x3f3f003f, 0x94940094, 0x5c5c005c, 0x02020002, + 0x4a4a004a, 0x33330033, 0x67670067, 0xf3f300f3, 0x7f7f007f, 0xe2e200e2, + 0x9b9b009b, 0x26260026, 0x37370037, 0x3b3b003b, 0x96960096, 0x4b4b004b, + 0xbebe00be, 0x2e2e002e, 0x79790079, 0x8c8c008c, 0x6e6e006e, 0x8e8e008e, + 0xf5f500f5, 0xb6b600b6, 0xfdfd00fd, 0x59590059, 0x98980098, 0x6a6a006a, + 0x46460046, 0xbaba00ba, 0x25250025, 0x42420042, 0xa2a200a2, 0xfafa00fa, + 0x07070007, 0x55550055, 0xeeee00ee, 0x0a0a000a, 0x49490049, 0x68680068, + 0x38380038, 0xa4a400a4, 0x28280028, 0x7b7b007b, 0xc9c900c9, 0xc1c100c1, + 0xe3e300e3, 0xf4f400f4, 0xc7c700c7, 0x9e9e009e + }; + + private static final int SBOX2_0222[] = { + 0x00e0e0e0, 0x00050505, 0x00585858, 0x00d9d9d9, 0x00676767, 0x004e4e4e, + 0x00818181, 0x00cbcbcb, 0x00c9c9c9, 0x000b0b0b, 0x00aeaeae, 0x006a6a6a, + 0x00d5d5d5, 0x00181818, 0x005d5d5d, 0x00828282, 0x00464646, 0x00dfdfdf, + 0x00d6d6d6, 0x00272727, 0x008a8a8a, 0x00323232, 0x004b4b4b, 0x00424242, + 0x00dbdbdb, 0x001c1c1c, 0x009e9e9e, 0x009c9c9c, 0x003a3a3a, 0x00cacaca, + 0x00252525, 0x007b7b7b, 0x000d0d0d, 0x00717171, 0x005f5f5f, 0x001f1f1f, + 0x00f8f8f8, 0x00d7d7d7, 0x003e3e3e, 0x009d9d9d, 0x007c7c7c, 0x00606060, + 0x00b9b9b9, 0x00bebebe, 0x00bcbcbc, 0x008b8b8b, 0x00161616, 0x00343434, + 0x004d4d4d, 0x00c3c3c3, 0x00727272, 0x00959595, 0x00ababab, 0x008e8e8e, + 0x00bababa, 0x007a7a7a, 0x00b3b3b3, 0x00020202, 0x00b4b4b4, 0x00adadad, + 0x00a2a2a2, 0x00acacac, 0x00d8d8d8, 0x009a9a9a, 0x00171717, 0x001a1a1a, + 0x00353535, 0x00cccccc, 0x00f7f7f7, 0x00999999, 0x00616161, 0x005a5a5a, + 0x00e8e8e8, 0x00242424, 0x00565656, 0x00404040, 0x00e1e1e1, 0x00636363, + 0x00090909, 0x00333333, 0x00bfbfbf, 0x00989898, 0x00979797, 0x00858585, + 0x00686868, 0x00fcfcfc, 0x00ececec, 0x000a0a0a, 0x00dadada, 0x006f6f6f, + 0x00535353, 0x00626262, 0x00a3a3a3, 0x002e2e2e, 0x00080808, 0x00afafaf, + 0x00282828, 0x00b0b0b0, 0x00747474, 0x00c2c2c2, 0x00bdbdbd, 0x00363636, + 0x00222222, 0x00383838, 0x00646464, 0x001e1e1e, 0x00393939, 0x002c2c2c, + 0x00a6a6a6, 0x00303030, 0x00e5e5e5, 0x00444444, 0x00fdfdfd, 0x00888888, + 0x009f9f9f, 0x00656565, 0x00878787, 0x006b6b6b, 0x00f4f4f4, 0x00232323, + 0x00484848, 0x00101010, 0x00d1d1d1, 0x00515151, 0x00c0c0c0, 0x00f9f9f9, + 0x00d2d2d2, 0x00a0a0a0, 0x00555555, 0x00a1a1a1, 0x00414141, 0x00fafafa, + 0x00434343, 0x00131313, 0x00c4c4c4, 0x002f2f2f, 0x00a8a8a8, 0x00b6b6b6, + 0x003c3c3c, 0x002b2b2b, 0x00c1c1c1, 0x00ffffff, 0x00c8c8c8, 0x00a5a5a5, + 0x00202020, 0x00898989, 0x00000000, 0x00909090, 0x00474747, 0x00efefef, + 0x00eaeaea, 0x00b7b7b7, 0x00151515, 0x00060606, 0x00cdcdcd, 0x00b5b5b5, + 0x00121212, 0x007e7e7e, 0x00bbbbbb, 0x00292929, 0x000f0f0f, 0x00b8b8b8, + 0x00070707, 0x00040404, 0x009b9b9b, 0x00949494, 0x00212121, 0x00666666, + 0x00e6e6e6, 0x00cecece, 0x00ededed, 0x00e7e7e7, 0x003b3b3b, 0x00fefefe, + 0x007f7f7f, 0x00c5c5c5, 0x00a4a4a4, 0x00373737, 0x00b1b1b1, 0x004c4c4c, + 0x00919191, 0x006e6e6e, 0x008d8d8d, 0x00767676, 0x00030303, 0x002d2d2d, + 0x00dedede, 0x00969696, 0x00262626, 0x007d7d7d, 0x00c6c6c6, 0x005c5c5c, + 0x00d3d3d3, 0x00f2f2f2, 0x004f4f4f, 0x00191919, 0x003f3f3f, 0x00dcdcdc, + 0x00797979, 0x001d1d1d, 0x00525252, 0x00ebebeb, 0x00f3f3f3, 0x006d6d6d, + 0x005e5e5e, 0x00fbfbfb, 0x00696969, 0x00b2b2b2, 0x00f0f0f0, 0x00313131, + 0x000c0c0c, 0x00d4d4d4, 0x00cfcfcf, 0x008c8c8c, 0x00e2e2e2, 0x00757575, + 0x00a9a9a9, 0x004a4a4a, 0x00575757, 0x00848484, 0x00111111, 0x00454545, + 0x001b1b1b, 0x00f5f5f5, 0x00e4e4e4, 0x000e0e0e, 0x00737373, 0x00aaaaaa, + 0x00f1f1f1, 0x00dddddd, 0x00595959, 0x00141414, 0x006c6c6c, 0x00929292, + 0x00545454, 0x00d0d0d0, 0x00787878, 0x00707070, 0x00e3e3e3, 0x00494949, + 0x00808080, 0x00505050, 0x00a7a7a7, 0x00f6f6f6, 0x00777777, 0x00939393, + 0x00868686, 0x00838383, 0x002a2a2a, 0x00c7c7c7, 0x005b5b5b, 0x00e9e9e9, + 0x00eeeeee, 0x008f8f8f, 0x00010101, 0x003d3d3d + }; + + private static final int SBOX3_3033[] = { + 0x38003838, 0x41004141, 0x16001616, 0x76007676, 0xd900d9d9, 0x93009393, + 0x60006060, 0xf200f2f2, 0x72007272, 0xc200c2c2, 0xab00abab, 0x9a009a9a, + 0x75007575, 0x06000606, 0x57005757, 0xa000a0a0, 0x91009191, 0xf700f7f7, + 0xb500b5b5, 0xc900c9c9, 0xa200a2a2, 0x8c008c8c, 0xd200d2d2, 0x90009090, + 0xf600f6f6, 0x07000707, 0xa700a7a7, 0x27002727, 0x8e008e8e, 0xb200b2b2, + 0x49004949, 0xde00dede, 0x43004343, 0x5c005c5c, 0xd700d7d7, 0xc700c7c7, + 0x3e003e3e, 0xf500f5f5, 0x8f008f8f, 0x67006767, 0x1f001f1f, 0x18001818, + 0x6e006e6e, 0xaf00afaf, 0x2f002f2f, 0xe200e2e2, 0x85008585, 0x0d000d0d, + 0x53005353, 0xf000f0f0, 0x9c009c9c, 0x65006565, 0xea00eaea, 0xa300a3a3, + 0xae00aeae, 0x9e009e9e, 0xec00ecec, 0x80008080, 0x2d002d2d, 0x6b006b6b, + 0xa800a8a8, 0x2b002b2b, 0x36003636, 0xa600a6a6, 0xc500c5c5, 0x86008686, + 0x4d004d4d, 0x33003333, 0xfd00fdfd, 0x66006666, 0x58005858, 0x96009696, + 0x3a003a3a, 0x09000909, 0x95009595, 0x10001010, 0x78007878, 0xd800d8d8, + 0x42004242, 0xcc00cccc, 0xef00efef, 0x26002626, 0xe500e5e5, 0x61006161, + 0x1a001a1a, 0x3f003f3f, 0x3b003b3b, 0x82008282, 0xb600b6b6, 0xdb00dbdb, + 0xd400d4d4, 0x98009898, 0xe800e8e8, 0x8b008b8b, 0x02000202, 0xeb00ebeb, + 0x0a000a0a, 0x2c002c2c, 0x1d001d1d, 0xb000b0b0, 0x6f006f6f, 0x8d008d8d, + 0x88008888, 0x0e000e0e, 0x19001919, 0x87008787, 0x4e004e4e, 0x0b000b0b, + 0xa900a9a9, 0x0c000c0c, 0x79007979, 0x11001111, 0x7f007f7f, 0x22002222, + 0xe700e7e7, 0x59005959, 0xe100e1e1, 0xda00dada, 0x3d003d3d, 0xc800c8c8, + 0x12001212, 0x04000404, 0x74007474, 0x54005454, 0x30003030, 0x7e007e7e, + 0xb400b4b4, 0x28002828, 0x55005555, 0x68006868, 0x50005050, 0xbe00bebe, + 0xd000d0d0, 0xc400c4c4, 0x31003131, 0xcb00cbcb, 0x2a002a2a, 0xad00adad, + 0x0f000f0f, 0xca00caca, 0x70007070, 0xff00ffff, 0x32003232, 0x69006969, + 0x08000808, 0x62006262, 0x00000000, 0x24002424, 0xd100d1d1, 0xfb00fbfb, + 0xba00baba, 0xed00eded, 0x45004545, 0x81008181, 0x73007373, 0x6d006d6d, + 0x84008484, 0x9f009f9f, 0xee00eeee, 0x4a004a4a, 0xc300c3c3, 0x2e002e2e, + 0xc100c1c1, 0x01000101, 0xe600e6e6, 0x25002525, 0x48004848, 0x99009999, + 0xb900b9b9, 0xb300b3b3, 0x7b007b7b, 0xf900f9f9, 0xce00cece, 0xbf00bfbf, + 0xdf00dfdf, 0x71007171, 0x29002929, 0xcd00cdcd, 0x6c006c6c, 0x13001313, + 0x64006464, 0x9b009b9b, 0x63006363, 0x9d009d9d, 0xc000c0c0, 0x4b004b4b, + 0xb700b7b7, 0xa500a5a5, 0x89008989, 0x5f005f5f, 0xb100b1b1, 0x17001717, + 0xf400f4f4, 0xbc00bcbc, 0xd300d3d3, 0x46004646, 0xcf00cfcf, 0x37003737, + 0x5e005e5e, 0x47004747, 0x94009494, 0xfa00fafa, 0xfc00fcfc, 0x5b005b5b, + 0x97009797, 0xfe00fefe, 0x5a005a5a, 0xac00acac, 0x3c003c3c, 0x4c004c4c, + 0x03000303, 0x35003535, 0xf300f3f3, 0x23002323, 0xb800b8b8, 0x5d005d5d, + 0x6a006a6a, 0x92009292, 0xd500d5d5, 0x21002121, 0x44004444, 0x51005151, + 0xc600c6c6, 0x7d007d7d, 0x39003939, 0x83008383, 0xdc00dcdc, 0xaa00aaaa, + 0x7c007c7c, 0x77007777, 0x56005656, 0x05000505, 0x1b001b1b, 0xa400a4a4, + 0x15001515, 0x34003434, 0x1e001e1e, 0x1c001c1c, 0xf800f8f8, 0x52005252, + 0x20002020, 0x14001414, 0xe900e9e9, 0xbd00bdbd, 0xdd00dddd, 0xe400e4e4, + 0xa100a1a1, 0xe000e0e0, 0x8a008a8a, 0xf100f1f1, 0xd600d6d6, 0x7a007a7a, + 0xbb00bbbb, 0xe300e3e3, 0x40004040, 0x4f004f4f + }; + + private static int rightRotate(int x, int s) + { + return (((x) >>> (s)) + ((x) << (32 - s))); + } + + private static int leftRotate(int x, int s) + { + return ((x) << (s)) + ((x) >>> (32 - s)); + } + + private static void roldq(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[0 + ooff] = (ki[0 + ioff] << rot) | (ki[1 + ioff] >>> (32 - rot)); + ko[1 + ooff] = (ki[1 + ioff] << rot) | (ki[2 + ioff] >>> (32 - rot)); + ko[2 + ooff] = (ki[2 + ioff] << rot) | (ki[3 + ioff] >>> (32 - rot)); + ko[3 + ooff] = (ki[3 + ioff] << rot) | (ki[0 + ioff] >>> (32 - rot)); + ki[0 + ioff] = ko[0 + ooff]; + ki[1 + ioff] = ko[1 + ooff]; + ki[2 + ioff] = ko[2 + ooff]; + ki[3 + ioff] = ko[3 + ooff]; + } + + private static void decroldq(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[2 + ooff] = (ki[0 + ioff] << rot) | (ki[1 + ioff] >>> (32 - rot)); + ko[3 + ooff] = (ki[1 + ioff] << rot) | (ki[2 + ioff] >>> (32 - rot)); + ko[0 + ooff] = (ki[2 + ioff] << rot) | (ki[3 + ioff] >>> (32 - rot)); + ko[1 + ooff] = (ki[3 + ioff] << rot) | (ki[0 + ioff] >>> (32 - rot)); + ki[0 + ioff] = ko[2 + ooff]; + ki[1 + ioff] = ko[3 + ooff]; + ki[2 + ioff] = ko[0 + ooff]; + ki[3 + ioff] = ko[1 + ooff]; + } + + private static void roldqo32(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[0 + ooff] = (ki[1 + ioff] << (rot - 32)) | (ki[2 + ioff] >>> (64 - rot)); + ko[1 + ooff] = (ki[2 + ioff] << (rot - 32)) | (ki[3 + ioff] >>> (64 - rot)); + ko[2 + ooff] = (ki[3 + ioff] << (rot - 32)) | (ki[0 + ioff] >>> (64 - rot)); + ko[3 + ooff] = (ki[0 + ioff] << (rot - 32)) | (ki[1 + ioff] >>> (64 - rot)); + ki[0 + ioff] = ko[0 + ooff]; + ki[1 + ioff] = ko[1 + ooff]; + ki[2 + ioff] = ko[2 + ooff]; + ki[3 + ioff] = ko[3 + ooff]; + } + + private static void decroldqo32(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[2 + ooff] = (ki[1 + ioff] << (rot - 32)) | (ki[2 + ioff] >>> (64 - rot)); + ko[3 + ooff] = (ki[2 + ioff] << (rot - 32)) | (ki[3 + ioff] >>> (64 - rot)); + ko[0 + ooff] = (ki[3 + ioff] << (rot - 32)) | (ki[0 + ioff] >>> (64 - rot)); + ko[1 + ooff] = (ki[0 + ioff] << (rot - 32)) | (ki[1 + ioff] >>> (64 - rot)); + ki[0 + ioff] = ko[2 + ooff]; + ki[1 + ioff] = ko[3 + ooff]; + ki[2 + ioff] = ko[0 + ooff]; + ki[3 + ioff] = ko[1 + ooff]; + } + + private int bytes2int(byte[] src, int offset) + { + int word = 0; + + for (int i = 0; i < 4; i++) + { + word = (word << 8) + (src[i + offset] & MASK8); + } + return word; + } + + private void int2bytes(int word, byte[] dst, int offset) + { + for (int i = 0; i < 4; i++) + { + dst[(3 - i) + offset] = (byte)word; + word >>>= 8; + } + } + + private void camelliaF2(int[] s, int[] skey, int keyoff) + { + int t1, t2, u, v; + + t1 = s[0] ^ skey[0 + keyoff]; + u = SBOX4_4404[t1 & MASK8]; + u ^= SBOX3_3033[(t1 >>> 8) & MASK8]; + u ^= SBOX2_0222[(t1 >>> 16) & MASK8]; + u ^= SBOX1_1110[(t1 >>> 24) & MASK8]; + t2 = s[1] ^ skey[1 + keyoff]; + v = SBOX1_1110[t2 & MASK8]; + v ^= SBOX4_4404[(t2 >>> 8) & MASK8]; + v ^= SBOX3_3033[(t2 >>> 16) & MASK8]; + v ^= SBOX2_0222[(t2 >>> 24) & MASK8]; + + s[2] ^= u ^ v; + s[3] ^= u ^ v ^ rightRotate(u, 8); + + t1 = s[2] ^ skey[2 + keyoff]; + u = SBOX4_4404[t1 & MASK8]; + u ^= SBOX3_3033[(t1 >>> 8) & MASK8]; + u ^= SBOX2_0222[(t1 >>> 16) & MASK8]; + u ^= SBOX1_1110[(t1 >>> 24) & MASK8]; + t2 = s[3] ^ skey[3 + keyoff]; + v = SBOX1_1110[t2 & MASK8]; + v ^= SBOX4_4404[(t2 >>> 8) & MASK8]; + v ^= SBOX3_3033[(t2 >>> 16) & MASK8]; + v ^= SBOX2_0222[(t2 >>> 24) & MASK8]; + + s[0] ^= u ^ v; + s[1] ^= u ^ v ^ rightRotate(u, 8); + } + + private void camelliaFLs(int[] s, int[] fkey, int keyoff) + { + + s[1] ^= leftRotate(s[0] & fkey[0 + keyoff], 1); + s[0] ^= fkey[1 + keyoff] | s[1]; + + s[2] ^= fkey[3 + keyoff] | s[3]; + s[3] ^= leftRotate(fkey[2 + keyoff] & s[2], 1); + } + + private void setKey(boolean forEncryption, byte[] key) + { + int[] k = new int[8]; + int[] ka = new int[4]; + int[] kb = new int[4]; + int[] t = new int[4]; + + switch (key.length) + { + case 16: + _keyIs128 = true; + k[0] = bytes2int(key, 0); + k[1] = bytes2int(key, 4); + k[2] = bytes2int(key, 8); + k[3] = bytes2int(key, 12); + k[4] = k[5] = k[6] = k[7] = 0; + break; + case 24: + k[0] = bytes2int(key, 0); + k[1] = bytes2int(key, 4); + k[2] = bytes2int(key, 8); + k[3] = bytes2int(key, 12); + k[4] = bytes2int(key, 16); + k[5] = bytes2int(key, 20); + k[6] = ~k[4]; + k[7] = ~k[5]; + _keyIs128 = false; + break; + case 32: + k[0] = bytes2int(key, 0); + k[1] = bytes2int(key, 4); + k[2] = bytes2int(key, 8); + k[3] = bytes2int(key, 12); + k[4] = bytes2int(key, 16); + k[5] = bytes2int(key, 20); + k[6] = bytes2int(key, 24); + k[7] = bytes2int(key, 28); + _keyIs128 = false; + break; + default: + throw new + IllegalArgumentException("key sizes are only 16/24/32 bytes."); + } + + for (int i = 0; i < 4; i++) + { + ka[i] = k[i] ^ k[i + 4]; + } + /* compute KA */ + camelliaF2(ka, SIGMA, 0); + for (int i = 0; i < 4; i++) + { + ka[i] ^= k[i]; + } + camelliaF2(ka, SIGMA, 4); + + if (_keyIs128) + { + if (forEncryption) + { + /* KL dependant keys */ + kw[0] = k[0]; + kw[1] = k[1]; + kw[2] = k[2]; + kw[3] = k[3]; + roldq(15, k, 0, subkey, 4); + roldq(30, k, 0, subkey, 12); + roldq(15, k, 0, t, 0); + subkey[18] = t[2]; + subkey[19] = t[3]; + roldq(17, k, 0, ke, 4); + roldq(17, k, 0, subkey, 24); + roldq(17, k, 0, subkey, 32); + /* KA dependant keys */ + subkey[0] = ka[0]; + subkey[1] = ka[1]; + subkey[2] = ka[2]; + subkey[3] = ka[3]; + roldq(15, ka, 0, subkey, 8); + roldq(15, ka, 0, ke, 0); + roldq(15, ka, 0, t, 0); + subkey[16] = t[0]; + subkey[17] = t[1]; + roldq(15, ka, 0, subkey, 20); + roldqo32(34, ka, 0, subkey, 28); + roldq(17, ka, 0, kw, 4); + + } + else + { // decryption + /* KL dependant keys */ + kw[4] = k[0]; + kw[5] = k[1]; + kw[6] = k[2]; + kw[7] = k[3]; + decroldq(15, k, 0, subkey, 28); + decroldq(30, k, 0, subkey, 20); + decroldq(15, k, 0, t, 0); + subkey[16] = t[0]; + subkey[17] = t[1]; + decroldq(17, k, 0, ke, 0); + decroldq(17, k, 0, subkey, 8); + decroldq(17, k, 0, subkey, 0); + /* KA dependant keys */ + subkey[34] = ka[0]; + subkey[35] = ka[1]; + subkey[32] = ka[2]; + subkey[33] = ka[3]; + decroldq(15, ka, 0, subkey, 24); + decroldq(15, ka, 0, ke, 4); + decroldq(15, ka, 0, t, 0); + subkey[18] = t[2]; + subkey[19] = t[3]; + decroldq(15, ka, 0, subkey, 12); + decroldqo32(34, ka, 0, subkey, 4); + roldq(17, ka, 0, kw, 0); + } + } + else + { // 192bit or 256bit + /* compute KB */ + for (int i = 0; i < 4; i++) + { + kb[i] = ka[i] ^ k[i + 4]; + } + camelliaF2(kb, SIGMA, 8); + + if (forEncryption) + { + /* KL dependant keys */ + kw[0] = k[0]; + kw[1] = k[1]; + kw[2] = k[2]; + kw[3] = k[3]; + roldqo32(45, k, 0, subkey, 16); + roldq(15, k, 0, ke, 4); + roldq(17, k, 0, subkey, 32); + roldqo32(34, k, 0, subkey, 44); + /* KR dependant keys */ + roldq(15, k, 4, subkey, 4); + roldq(15, k, 4, ke, 0); + roldq(30, k, 4, subkey, 24); + roldqo32(34, k, 4, subkey, 36); + /* KA dependant keys */ + roldq(15, ka, 0, subkey, 8); + roldq(30, ka, 0, subkey, 20); + /* 32bit rotation */ + ke[8] = ka[1]; + ke[9] = ka[2]; + ke[10] = ka[3]; + ke[11] = ka[0]; + roldqo32(49, ka, 0, subkey, 40); + + /* KB dependant keys */ + subkey[0] = kb[0]; + subkey[1] = kb[1]; + subkey[2] = kb[2]; + subkey[3] = kb[3]; + roldq(30, kb, 0, subkey, 12); + roldq(30, kb, 0, subkey, 28); + roldqo32(51, kb, 0, kw, 4); + + } + else + { // decryption + /* KL dependant keys */ + kw[4] = k[0]; + kw[5] = k[1]; + kw[6] = k[2]; + kw[7] = k[3]; + decroldqo32(45, k, 0, subkey, 28); + decroldq(15, k, 0, ke, 4); + decroldq(17, k, 0, subkey, 12); + decroldqo32(34, k, 0, subkey, 0); + /* KR dependant keys */ + decroldq(15, k, 4, subkey, 40); + decroldq(15, k, 4, ke, 8); + decroldq(30, k, 4, subkey, 20); + decroldqo32(34, k, 4, subkey, 8); + /* KA dependant keys */ + decroldq(15, ka, 0, subkey, 36); + decroldq(30, ka, 0, subkey, 24); + /* 32bit rotation */ + ke[2] = ka[1]; + ke[3] = ka[2]; + ke[0] = ka[3]; + ke[1] = ka[0]; + decroldqo32(49, ka, 0, subkey, 4); + + /* KB dependant keys */ + subkey[46] = kb[0]; + subkey[47] = kb[1]; + subkey[44] = kb[2]; + subkey[45] = kb[3]; + decroldq(30, kb, 0, subkey, 32); + decroldq(30, kb, 0, subkey, 16); + roldqo32(51, kb, 0, kw, 0); + } + } + } + + private int processBlock128(byte[] in, int inOff, + byte[] out, int outOff) + { + for (int i = 0; i < 4; i++) + { + state[i] = bytes2int(in, inOff + (i * 4)); + state[i] ^= kw[i]; + } + + camelliaF2(state, subkey, 0); + camelliaF2(state, subkey, 4); + camelliaF2(state, subkey, 8); + camelliaFLs(state, ke, 0); + camelliaF2(state, subkey, 12); + camelliaF2(state, subkey, 16); + camelliaF2(state, subkey, 20); + camelliaFLs(state, ke, 4); + camelliaF2(state, subkey, 24); + camelliaF2(state, subkey, 28); + camelliaF2(state, subkey, 32); + + state[2] ^= kw[4]; + state[3] ^= kw[5]; + state[0] ^= kw[6]; + state[1] ^= kw[7]; + + int2bytes(state[2], out, outOff); + int2bytes(state[3], out, outOff + 4); + int2bytes(state[0], out, outOff + 8); + int2bytes(state[1], out, outOff + 12); + + return BLOCK_SIZE; + } + + private int processBlock192or256(byte[] in, int inOff, + byte[] out, int outOff) + { + for (int i = 0; i < 4; i++) + { + state[i] = bytes2int(in, inOff + (i * 4)); + state[i] ^= kw[i]; + } + + camelliaF2(state, subkey, 0); + camelliaF2(state, subkey, 4); + camelliaF2(state, subkey, 8); + camelliaFLs(state, ke, 0); + camelliaF2(state, subkey, 12); + camelliaF2(state, subkey, 16); + camelliaF2(state, subkey, 20); + camelliaFLs(state, ke, 4); + camelliaF2(state, subkey, 24); + camelliaF2(state, subkey, 28); + camelliaF2(state, subkey, 32); + camelliaFLs(state, ke, 8); + camelliaF2(state, subkey, 36); + camelliaF2(state, subkey, 40); + camelliaF2(state, subkey, 44); + + state[2] ^= kw[4]; + state[3] ^= kw[5]; + state[0] ^= kw[6]; + state[1] ^= kw[7]; + + int2bytes(state[2], out, outOff); + int2bytes(state[3], out, outOff + 4); + int2bytes(state[0], out, outOff + 8); + int2bytes(state[1], out, outOff + 12); + return BLOCK_SIZE; + } + + public CamelliaEngine() + { + } + + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("only simple KeyParameter expected."); + } + + setKey(forEncryption, ((KeyParameter)params).getKey()); + initialised = true; + } + + public String getAlgorithmName() + { + return "Camellia"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if (!initialised) + { + throw new IllegalStateException("Camellia engine not initialised"); + } + + if ((inOff + BLOCK_SIZE) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + BLOCK_SIZE) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (_keyIs128) + { + return processBlock128(in, inOff, out, outOff); + } + else + { + return processBlock192or256(in, inOff, out, outOff); + } + } + + public void reset() + { + // nothing + + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/CamelliaLightEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/CamelliaLightEngine.java new file mode 100644 index 000000000..f6c3dc0ce --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/CamelliaLightEngine.java @@ -0,0 +1,591 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * Camellia - based on RFC 3713, smaller implementation, about half the size of CamelliaEngine. + */ + +public class CamelliaLightEngine + implements BlockCipher +{ + private static final int BLOCK_SIZE = 16; + private static final int MASK8 = 0xff; + private boolean initialized; + private boolean _keyis128; + + private int[] subkey = new int[24 * 4]; + private int[] kw = new int[4 * 2]; // for whitening + private int[] ke = new int[6 * 2]; // for FL and FL^(-1) + private int[] state = new int[4]; // for encryption and decryption + + private static final int SIGMA[] = { + 0xa09e667f, 0x3bcc908b, + 0xb67ae858, 0x4caa73b2, + 0xc6ef372f, 0xe94f82be, + 0x54ff53a5, 0xf1d36f1c, + 0x10e527fa, 0xde682d1d, + 0xb05688c2, 0xb3e6c1fd + }; + + /* + * + * S-box data + * + */ + private static final byte SBOX1[] = { + (byte)112, (byte)130, (byte)44, (byte)236, + (byte)179, (byte)39, (byte)192, (byte)229, + (byte)228, (byte)133, (byte)87, (byte)53, + (byte)234, (byte)12, (byte)174, (byte)65, + (byte)35, (byte)239, (byte)107, (byte)147, + (byte)69, (byte)25, (byte)165, (byte)33, + (byte)237, (byte)14, (byte)79, (byte)78, + (byte)29, (byte)101, (byte)146, (byte)189, + (byte)134, (byte)184, (byte)175, (byte)143, + (byte)124, (byte)235, (byte)31, (byte)206, + (byte)62, (byte)48, (byte)220, (byte)95, + (byte)94, (byte)197, (byte)11, (byte)26, + (byte)166, (byte)225, (byte)57, (byte)202, + (byte)213, (byte)71, (byte)93, (byte)61, + (byte)217, (byte)1, (byte)90, (byte)214, + (byte)81, (byte)86, (byte)108, (byte)77, + (byte)139, (byte)13, (byte)154, (byte)102, + (byte)251, (byte)204, (byte)176, (byte)45, + (byte)116, (byte)18, (byte)43, (byte)32, + (byte)240, (byte)177, (byte)132, (byte)153, + (byte)223, (byte)76, (byte)203, (byte)194, + (byte)52, (byte)126, (byte)118, (byte)5, + (byte)109, (byte)183, (byte)169, (byte)49, + (byte)209, (byte)23, (byte)4, (byte)215, + (byte)20, (byte)88, (byte)58, (byte)97, + (byte)222, (byte)27, (byte)17, (byte)28, + (byte)50, (byte)15, (byte)156, (byte)22, + (byte)83, (byte)24, (byte)242, (byte)34, + (byte)254, (byte)68, (byte)207, (byte)178, + (byte)195, (byte)181, (byte)122, (byte)145, + (byte)36, (byte)8, (byte)232, (byte)168, + (byte)96, (byte)252, (byte)105, (byte)80, + (byte)170, (byte)208, (byte)160, (byte)125, + (byte)161, (byte)137, (byte)98, (byte)151, + (byte)84, (byte)91, (byte)30, (byte)149, + (byte)224, (byte)255, (byte)100, (byte)210, + (byte)16, (byte)196, (byte)0, (byte)72, + (byte)163, (byte)247, (byte)117, (byte)219, + (byte)138, (byte)3, (byte)230, (byte)218, + (byte)9, (byte)63, (byte)221, (byte)148, + (byte)135, (byte)92, (byte)131, (byte)2, + (byte)205, (byte)74, (byte)144, (byte)51, + (byte)115, (byte)103, (byte)246, (byte)243, + (byte)157, (byte)127, (byte)191, (byte)226, + (byte)82, (byte)155, (byte)216, (byte)38, + (byte)200, (byte)55, (byte)198, (byte)59, + (byte)129, (byte)150, (byte)111, (byte)75, + (byte)19, (byte)190, (byte)99, (byte)46, + (byte)233, (byte)121, (byte)167, (byte)140, + (byte)159, (byte)110, (byte)188, (byte)142, + (byte)41, (byte)245, (byte)249, (byte)182, + (byte)47, (byte)253, (byte)180, (byte)89, + (byte)120, (byte)152, (byte)6, (byte)106, + (byte)231, (byte)70, (byte)113, (byte)186, + (byte)212, (byte)37, (byte)171, (byte)66, + (byte)136, (byte)162, (byte)141, (byte)250, + (byte)114, (byte)7, (byte)185, (byte)85, + (byte)248, (byte)238, (byte)172, (byte)10, + (byte)54, (byte)73, (byte)42, (byte)104, + (byte)60, (byte)56, (byte)241, (byte)164, + (byte)64, (byte)40, (byte)211, (byte)123, + (byte)187, (byte)201, (byte)67, (byte)193, + (byte)21, (byte)227, (byte)173, (byte)244, + (byte)119, (byte)199, (byte)128, (byte)158 + }; + + private static int rightRotate(int x, int s) + { + return (((x) >>> (s)) + ((x) << (32 - s))); + } + + private static int leftRotate(int x, int s) + { + return ((x) << (s)) + ((x) >>> (32 - s)); + } + + private static void roldq(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[0 + ooff] = (ki[0 + ioff] << rot) | (ki[1 + ioff] >>> (32 - rot)); + ko[1 + ooff] = (ki[1 + ioff] << rot) | (ki[2 + ioff] >>> (32 - rot)); + ko[2 + ooff] = (ki[2 + ioff] << rot) | (ki[3 + ioff] >>> (32 - rot)); + ko[3 + ooff] = (ki[3 + ioff] << rot) | (ki[0 + ioff] >>> (32 - rot)); + ki[0 + ioff] = ko[0 + ooff]; + ki[1 + ioff] = ko[1 + ooff]; + ki[2 + ioff] = ko[2 + ooff]; + ki[3 + ioff] = ko[3 + ooff]; + } + + private static void decroldq(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[2 + ooff] = (ki[0 + ioff] << rot) | (ki[1 + ioff] >>> (32 - rot)); + ko[3 + ooff] = (ki[1 + ioff] << rot) | (ki[2 + ioff] >>> (32 - rot)); + ko[0 + ooff] = (ki[2 + ioff] << rot) | (ki[3 + ioff] >>> (32 - rot)); + ko[1 + ooff] = (ki[3 + ioff] << rot) | (ki[0 + ioff] >>> (32 - rot)); + ki[0 + ioff] = ko[2 + ooff]; + ki[1 + ioff] = ko[3 + ooff]; + ki[2 + ioff] = ko[0 + ooff]; + ki[3 + ioff] = ko[1 + ooff]; + } + + private static void roldqo32(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[0 + ooff] = (ki[1 + ioff] << (rot - 32)) | (ki[2 + ioff] >>> (64 - rot)); + ko[1 + ooff] = (ki[2 + ioff] << (rot - 32)) | (ki[3 + ioff] >>> (64 - rot)); + ko[2 + ooff] = (ki[3 + ioff] << (rot - 32)) | (ki[0 + ioff] >>> (64 - rot)); + ko[3 + ooff] = (ki[0 + ioff] << (rot - 32)) | (ki[1 + ioff] >>> (64 - rot)); + ki[0 + ioff] = ko[0 + ooff]; + ki[1 + ioff] = ko[1 + ooff]; + ki[2 + ioff] = ko[2 + ooff]; + ki[3 + ioff] = ko[3 + ooff]; + } + + private static void decroldqo32(int rot, int[] ki, int ioff, + int[] ko, int ooff) + { + ko[2 + ooff] = (ki[1 + ioff] << (rot - 32)) | (ki[2 + ioff] >>> (64 - rot)); + ko[3 + ooff] = (ki[2 + ioff] << (rot - 32)) | (ki[3 + ioff] >>> (64 - rot)); + ko[0 + ooff] = (ki[3 + ioff] << (rot - 32)) | (ki[0 + ioff] >>> (64 - rot)); + ko[1 + ooff] = (ki[0 + ioff] << (rot - 32)) | (ki[1 + ioff] >>> (64 - rot)); + ki[0 + ioff] = ko[2 + ooff]; + ki[1 + ioff] = ko[3 + ooff]; + ki[2 + ioff] = ko[0 + ooff]; + ki[3 + ioff] = ko[1 + ooff]; + } + + private int bytes2int(byte[] src, int offset) + { + int word = 0; + + for (int i = 0; i < 4; i++) + { + word = (word << 8) + (src[i + offset] & MASK8); + } + return word; + } + + private void int2bytes(int word, byte[] dst, int offset) + { + for (int i = 0; i < 4; i++) + { + dst[(3 - i) + offset] = (byte)word; + word >>>= 8; + } + } + + private byte lRot8(byte v, int rot) + { + return (byte)((v << rot) | ((v & 0xff) >>> (8 - rot))); + } + + private int sbox2(int x) + { + return (lRot8(SBOX1[x], 1) & MASK8); + } + + private int sbox3(int x) + { + return (lRot8(SBOX1[x], 7) & MASK8); + } + + private int sbox4(int x) + { + return (SBOX1[((int)lRot8((byte)x, 1) & MASK8)] & MASK8); + } + + private void camelliaF2(int[] s, int[] skey, int keyoff) + { + int t1, t2, u, v; + + t1 = s[0] ^ skey[0 + keyoff]; + u = sbox4((t1 & MASK8)); + u |= (sbox3(((t1 >>> 8) & MASK8)) << 8); + u |= (sbox2(((t1 >>> 16) & MASK8)) << 16); + u |= ((int)(SBOX1[((t1 >>> 24) & MASK8)] & MASK8) << 24); + + t2 = s[1] ^ skey[1 + keyoff]; + v = (int)SBOX1[(t2 & MASK8)] & MASK8; + v |= (sbox4(((t2 >>> 8) & MASK8)) << 8); + v |= (sbox3(((t2 >>> 16) & MASK8)) << 16); + v |= (sbox2(((t2 >>> 24) & MASK8)) << 24); + + v = leftRotate(v, 8); + u ^= v; + v = leftRotate(v, 8) ^ u; + u = rightRotate(u, 8) ^ v; + s[2] ^= leftRotate(v, 16) ^ u; + s[3] ^= leftRotate(u, 8); + + t1 = s[2] ^ skey[2 + keyoff]; + u = sbox4((t1 & MASK8)); + u |= sbox3(((t1 >>> 8) & MASK8)) << 8; + u |= sbox2(((t1 >>> 16) & MASK8)) << 16; + u |= ((int)SBOX1[((t1 >>> 24) & MASK8)] & MASK8) << 24; + + t2 = s[3] ^ skey[3 + keyoff]; + v = ((int)SBOX1[(t2 & MASK8)] & MASK8); + v |= sbox4(((t2 >>> 8) & MASK8)) << 8; + v |= sbox3(((t2 >>> 16) & MASK8)) << 16; + v |= sbox2(((t2 >>> 24) & MASK8)) << 24; + + v = leftRotate(v, 8); + u ^= v; + v = leftRotate(v, 8) ^ u; + u = rightRotate(u, 8) ^ v; + s[0] ^= leftRotate(v, 16) ^ u; + s[1] ^= leftRotate(u, 8); + } + + private void camelliaFLs(int[] s, int[] fkey, int keyoff) + { + + s[1] ^= leftRotate(s[0] & fkey[0 + keyoff], 1); + s[0] ^= fkey[1 + keyoff] | s[1]; + + s[2] ^= fkey[3 + keyoff] | s[3]; + s[3] ^= leftRotate(fkey[2 + keyoff] & s[2], 1); + } + + private void setKey(boolean forEncryption, byte[] key) + { + int[] k = new int[8]; + int[] ka = new int[4]; + int[] kb = new int[4]; + int[] t = new int[4]; + + switch (key.length) + { + case 16: + _keyis128 = true; + k[0] = bytes2int(key, 0); + k[1] = bytes2int(key, 4); + k[2] = bytes2int(key, 8); + k[3] = bytes2int(key, 12); + k[4] = k[5] = k[6] = k[7] = 0; + break; + case 24: + k[0] = bytes2int(key, 0); + k[1] = bytes2int(key, 4); + k[2] = bytes2int(key, 8); + k[3] = bytes2int(key, 12); + k[4] = bytes2int(key, 16); + k[5] = bytes2int(key, 20); + k[6] = ~k[4]; + k[7] = ~k[5]; + _keyis128 = false; + break; + case 32: + k[0] = bytes2int(key, 0); + k[1] = bytes2int(key, 4); + k[2] = bytes2int(key, 8); + k[3] = bytes2int(key, 12); + k[4] = bytes2int(key, 16); + k[5] = bytes2int(key, 20); + k[6] = bytes2int(key, 24); + k[7] = bytes2int(key, 28); + _keyis128 = false; + break; + default: + throw new + IllegalArgumentException("key sizes are only 16/24/32 bytes."); + } + + for (int i = 0; i < 4; i++) + { + ka[i] = k[i] ^ k[i + 4]; + } + /* compute KA */ + camelliaF2(ka, SIGMA, 0); + for (int i = 0; i < 4; i++) + { + ka[i] ^= k[i]; + } + camelliaF2(ka, SIGMA, 4); + + if (_keyis128) + { + if (forEncryption) + { + /* KL dependant keys */ + kw[0] = k[0]; + kw[1] = k[1]; + kw[2] = k[2]; + kw[3] = k[3]; + roldq(15, k, 0, subkey, 4); + roldq(30, k, 0, subkey, 12); + roldq(15, k, 0, t, 0); + subkey[18] = t[2]; + subkey[19] = t[3]; + roldq(17, k, 0, ke, 4); + roldq(17, k, 0, subkey, 24); + roldq(17, k, 0, subkey, 32); + /* KA dependant keys */ + subkey[0] = ka[0]; + subkey[1] = ka[1]; + subkey[2] = ka[2]; + subkey[3] = ka[3]; + roldq(15, ka, 0, subkey, 8); + roldq(15, ka, 0, ke, 0); + roldq(15, ka, 0, t, 0); + subkey[16] = t[0]; + subkey[17] = t[1]; + roldq(15, ka, 0, subkey, 20); + roldqo32(34, ka, 0, subkey, 28); + roldq(17, ka, 0, kw, 4); + + } + else + { // decryption + /* KL dependant keys */ + kw[4] = k[0]; + kw[5] = k[1]; + kw[6] = k[2]; + kw[7] = k[3]; + decroldq(15, k, 0, subkey, 28); + decroldq(30, k, 0, subkey, 20); + decroldq(15, k, 0, t, 0); + subkey[16] = t[0]; + subkey[17] = t[1]; + decroldq(17, k, 0, ke, 0); + decroldq(17, k, 0, subkey, 8); + decroldq(17, k, 0, subkey, 0); + /* KA dependant keys */ + subkey[34] = ka[0]; + subkey[35] = ka[1]; + subkey[32] = ka[2]; + subkey[33] = ka[3]; + decroldq(15, ka, 0, subkey, 24); + decroldq(15, ka, 0, ke, 4); + decroldq(15, ka, 0, t, 0); + subkey[18] = t[2]; + subkey[19] = t[3]; + decroldq(15, ka, 0, subkey, 12); + decroldqo32(34, ka, 0, subkey, 4); + roldq(17, ka, 0, kw, 0); + } + } + else + { // 192bit or 256bit + /* compute KB */ + for (int i = 0; i < 4; i++) + { + kb[i] = ka[i] ^ k[i + 4]; + } + camelliaF2(kb, SIGMA, 8); + + if (forEncryption) + { + /* KL dependant keys */ + kw[0] = k[0]; + kw[1] = k[1]; + kw[2] = k[2]; + kw[3] = k[3]; + roldqo32(45, k, 0, subkey, 16); + roldq(15, k, 0, ke, 4); + roldq(17, k, 0, subkey, 32); + roldqo32(34, k, 0, subkey, 44); + /* KR dependant keys */ + roldq(15, k, 4, subkey, 4); + roldq(15, k, 4, ke, 0); + roldq(30, k, 4, subkey, 24); + roldqo32(34, k, 4, subkey, 36); + /* KA dependant keys */ + roldq(15, ka, 0, subkey, 8); + roldq(30, ka, 0, subkey, 20); + /* 32bit rotation */ + ke[8] = ka[1]; + ke[9] = ka[2]; + ke[10] = ka[3]; + ke[11] = ka[0]; + roldqo32(49, ka, 0, subkey, 40); + + /* KB dependant keys */ + subkey[0] = kb[0]; + subkey[1] = kb[1]; + subkey[2] = kb[2]; + subkey[3] = kb[3]; + roldq(30, kb, 0, subkey, 12); + roldq(30, kb, 0, subkey, 28); + roldqo32(51, kb, 0, kw, 4); + + } + else + { // decryption + /* KL dependant keys */ + kw[4] = k[0]; + kw[5] = k[1]; + kw[6] = k[2]; + kw[7] = k[3]; + decroldqo32(45, k, 0, subkey, 28); + decroldq(15, k, 0, ke, 4); + decroldq(17, k, 0, subkey, 12); + decroldqo32(34, k, 0, subkey, 0); + /* KR dependant keys */ + decroldq(15, k, 4, subkey, 40); + decroldq(15, k, 4, ke, 8); + decroldq(30, k, 4, subkey, 20); + decroldqo32(34, k, 4, subkey, 8); + /* KA dependant keys */ + decroldq(15, ka, 0, subkey, 36); + decroldq(30, ka, 0, subkey, 24); + /* 32bit rotation */ + ke[2] = ka[1]; + ke[3] = ka[2]; + ke[0] = ka[3]; + ke[1] = ka[0]; + decroldqo32(49, ka, 0, subkey, 4); + + /* KB dependant keys */ + subkey[46] = kb[0]; + subkey[47] = kb[1]; + subkey[44] = kb[2]; + subkey[45] = kb[3]; + decroldq(30, kb, 0, subkey, 32); + decroldq(30, kb, 0, subkey, 16); + roldqo32(51, kb, 0, kw, 0); + } + } + } + + private int processBlock128(byte[] in, int inOff, + byte[] out, int outOff) + { + for (int i = 0; i < 4; i++) + { + state[i] = bytes2int(in, inOff + (i * 4)); + state[i] ^= kw[i]; + } + + camelliaF2(state, subkey, 0); + camelliaF2(state, subkey, 4); + camelliaF2(state, subkey, 8); + camelliaFLs(state, ke, 0); + camelliaF2(state, subkey, 12); + camelliaF2(state, subkey, 16); + camelliaF2(state, subkey, 20); + camelliaFLs(state, ke, 4); + camelliaF2(state, subkey, 24); + camelliaF2(state, subkey, 28); + camelliaF2(state, subkey, 32); + + state[2] ^= kw[4]; + state[3] ^= kw[5]; + state[0] ^= kw[6]; + state[1] ^= kw[7]; + + int2bytes(state[2], out, outOff); + int2bytes(state[3], out, outOff + 4); + int2bytes(state[0], out, outOff + 8); + int2bytes(state[1], out, outOff + 12); + + return BLOCK_SIZE; + } + + private int processBlock192or256(byte[] in, int inOff, + byte[] out, int outOff) + { + for (int i = 0; i < 4; i++) + { + state[i] = bytes2int(in, inOff + (i * 4)); + state[i] ^= kw[i]; + } + + camelliaF2(state, subkey, 0); + camelliaF2(state, subkey, 4); + camelliaF2(state, subkey, 8); + camelliaFLs(state, ke, 0); + camelliaF2(state, subkey, 12); + camelliaF2(state, subkey, 16); + camelliaF2(state, subkey, 20); + camelliaFLs(state, ke, 4); + camelliaF2(state, subkey, 24); + camelliaF2(state, subkey, 28); + camelliaF2(state, subkey, 32); + camelliaFLs(state, ke, 8); + camelliaF2(state, subkey, 36); + camelliaF2(state, subkey, 40); + camelliaF2(state, subkey, 44); + + state[2] ^= kw[4]; + state[3] ^= kw[5]; + state[0] ^= kw[6]; + state[1] ^= kw[7]; + + int2bytes(state[2], out, outOff); + int2bytes(state[3], out, outOff + 4); + int2bytes(state[0], out, outOff + 8); + int2bytes(state[1], out, outOff + 12); + return BLOCK_SIZE; + } + + public CamelliaLightEngine() + { + } + + public String getAlgorithmName() + { + return "Camellia"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public void init(boolean forEncryption, CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("only simple KeyParameter expected."); + } + + setKey(forEncryption, ((KeyParameter)params).getKey()); + initialized = true; + } + + public int processBlock(byte[] in, int inOff, + byte[] out, int outOff) + throws IllegalStateException + { + + if (!initialized) + { + throw new IllegalStateException("Camellia is not initialized"); + } + + if ((inOff + BLOCK_SIZE) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + BLOCK_SIZE) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (_keyis128) + { + return processBlock128(in, inOff, out, outOff); + } + else + { + return processBlock192or256(in, inOff, out, outOff); + } + } + + public void reset() + { + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/CamelliaWrapEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/CamelliaWrapEngine.java new file mode 100644 index 000000000..5f6814d51 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/CamelliaWrapEngine.java @@ -0,0 +1,15 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +/** + * An implementation of the Camellia key wrapper based on RFC 3657/RFC 3394. + *

+ * For further details see: http://www.ietf.org/rfc/rfc3657.txt. + */ +public class CamelliaWrapEngine + extends RFC3394WrapEngine +{ + public CamelliaWrapEngine() + { + super(new CamelliaEngine()); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/DESEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/DESEngine.java new file mode 100644 index 000000000..c00739499 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/DESEngine.java @@ -0,0 +1,494 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * a class that provides a basic DES engine. + */ +public class DESEngine + implements BlockCipher +{ + protected static final int BLOCK_SIZE = 8; + + private int[] workingKey = null; + + /** + * standard constructor. + */ + public DESEngine() + { + } + + /** + * initialise a DES cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + if (((KeyParameter)params).getKey().length > 8) + { + throw new IllegalArgumentException("DES key too long - should be 8 bytes"); + } + + workingKey = generateWorkingKey(encrypting, + ((KeyParameter)params).getKey()); + + return; + } + + throw new IllegalArgumentException("invalid parameter passed to DES init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "DES"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("DES engine not initialised"); + } + + if ((inOff + BLOCK_SIZE) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + BLOCK_SIZE) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + desFunc(workingKey, in, inOff, out, outOff); + + return BLOCK_SIZE; + } + + public void reset() + { + } + + /** + * what follows is mainly taken from "Applied Cryptography", by + * Bruce Schneier, however it also bears great resemblance to Richard + * Outerbridge's D3DES... + */ + +// private static final short[] Df_Key = +// { +// 0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef, +// 0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10, +// 0x89,0xab,0xcd,0xef,0x01,0x23,0x45,0x67 +// }; + + private static final short[] bytebit = + { + 0200, 0100, 040, 020, 010, 04, 02, 01 + }; + + private static final int[] bigbyte = + { + 0x800000, 0x400000, 0x200000, 0x100000, + 0x80000, 0x40000, 0x20000, 0x10000, + 0x8000, 0x4000, 0x2000, 0x1000, + 0x800, 0x400, 0x200, 0x100, + 0x80, 0x40, 0x20, 0x10, + 0x8, 0x4, 0x2, 0x1 + }; + + /* + * Use the key schedule specified in the Standard (ANSI X3.92-1981). + */ + + private static final byte[] pc1 = + { + 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, + 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, + 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, + 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3 + }; + + private static final byte[] totrot = + { + 1, 2, 4, 6, 8, 10, 12, 14, + 15, 17, 19, 21, 23, 25, 27, 28 + }; + + private static final byte[] pc2 = + { + 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, + 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, + 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, + 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 + }; + + private static final int[] SP1 = { + 0x01010400, 0x00000000, 0x00010000, 0x01010404, + 0x01010004, 0x00010404, 0x00000004, 0x00010000, + 0x00000400, 0x01010400, 0x01010404, 0x00000400, + 0x01000404, 0x01010004, 0x01000000, 0x00000004, + 0x00000404, 0x01000400, 0x01000400, 0x00010400, + 0x00010400, 0x01010000, 0x01010000, 0x01000404, + 0x00010004, 0x01000004, 0x01000004, 0x00010004, + 0x00000000, 0x00000404, 0x00010404, 0x01000000, + 0x00010000, 0x01010404, 0x00000004, 0x01010000, + 0x01010400, 0x01000000, 0x01000000, 0x00000400, + 0x01010004, 0x00010000, 0x00010400, 0x01000004, + 0x00000400, 0x00000004, 0x01000404, 0x00010404, + 0x01010404, 0x00010004, 0x01010000, 0x01000404, + 0x01000004, 0x00000404, 0x00010404, 0x01010400, + 0x00000404, 0x01000400, 0x01000400, 0x00000000, + 0x00010004, 0x00010400, 0x00000000, 0x01010004 + }; + + private static final int[] SP2 = { + 0x80108020, 0x80008000, 0x00008000, 0x00108020, + 0x00100000, 0x00000020, 0x80100020, 0x80008020, + 0x80000020, 0x80108020, 0x80108000, 0x80000000, + 0x80008000, 0x00100000, 0x00000020, 0x80100020, + 0x00108000, 0x00100020, 0x80008020, 0x00000000, + 0x80000000, 0x00008000, 0x00108020, 0x80100000, + 0x00100020, 0x80000020, 0x00000000, 0x00108000, + 0x00008020, 0x80108000, 0x80100000, 0x00008020, + 0x00000000, 0x00108020, 0x80100020, 0x00100000, + 0x80008020, 0x80100000, 0x80108000, 0x00008000, + 0x80100000, 0x80008000, 0x00000020, 0x80108020, + 0x00108020, 0x00000020, 0x00008000, 0x80000000, + 0x00008020, 0x80108000, 0x00100000, 0x80000020, + 0x00100020, 0x80008020, 0x80000020, 0x00100020, + 0x00108000, 0x00000000, 0x80008000, 0x00008020, + 0x80000000, 0x80100020, 0x80108020, 0x00108000 + }; + + private static final int[] SP3 = { + 0x00000208, 0x08020200, 0x00000000, 0x08020008, + 0x08000200, 0x00000000, 0x00020208, 0x08000200, + 0x00020008, 0x08000008, 0x08000008, 0x00020000, + 0x08020208, 0x00020008, 0x08020000, 0x00000208, + 0x08000000, 0x00000008, 0x08020200, 0x00000200, + 0x00020200, 0x08020000, 0x08020008, 0x00020208, + 0x08000208, 0x00020200, 0x00020000, 0x08000208, + 0x00000008, 0x08020208, 0x00000200, 0x08000000, + 0x08020200, 0x08000000, 0x00020008, 0x00000208, + 0x00020000, 0x08020200, 0x08000200, 0x00000000, + 0x00000200, 0x00020008, 0x08020208, 0x08000200, + 0x08000008, 0x00000200, 0x00000000, 0x08020008, + 0x08000208, 0x00020000, 0x08000000, 0x08020208, + 0x00000008, 0x00020208, 0x00020200, 0x08000008, + 0x08020000, 0x08000208, 0x00000208, 0x08020000, + 0x00020208, 0x00000008, 0x08020008, 0x00020200 + }; + + private static final int[] SP4 = { + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802080, 0x00800081, 0x00800001, 0x00002001, + 0x00000000, 0x00802000, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00800080, 0x00800001, + 0x00000001, 0x00002000, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002001, 0x00002080, + 0x00800081, 0x00000001, 0x00002080, 0x00800080, + 0x00002000, 0x00802080, 0x00802081, 0x00000081, + 0x00800080, 0x00800001, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00000000, 0x00802000, + 0x00002080, 0x00800080, 0x00800081, 0x00000001, + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802081, 0x00000081, 0x00000001, 0x00002000, + 0x00800001, 0x00002001, 0x00802080, 0x00800081, + 0x00002001, 0x00002080, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002000, 0x00802080 + }; + + private static final int[] SP5 = { + 0x00000100, 0x02080100, 0x02080000, 0x42000100, + 0x00080000, 0x00000100, 0x40000000, 0x02080000, + 0x40080100, 0x00080000, 0x02000100, 0x40080100, + 0x42000100, 0x42080000, 0x00080100, 0x40000000, + 0x02000000, 0x40080000, 0x40080000, 0x00000000, + 0x40000100, 0x42080100, 0x42080100, 0x02000100, + 0x42080000, 0x40000100, 0x00000000, 0x42000000, + 0x02080100, 0x02000000, 0x42000000, 0x00080100, + 0x00080000, 0x42000100, 0x00000100, 0x02000000, + 0x40000000, 0x02080000, 0x42000100, 0x40080100, + 0x02000100, 0x40000000, 0x42080000, 0x02080100, + 0x40080100, 0x00000100, 0x02000000, 0x42080000, + 0x42080100, 0x00080100, 0x42000000, 0x42080100, + 0x02080000, 0x00000000, 0x40080000, 0x42000000, + 0x00080100, 0x02000100, 0x40000100, 0x00080000, + 0x00000000, 0x40080000, 0x02080100, 0x40000100 + }; + + private static final int[] SP6 = { + 0x20000010, 0x20400000, 0x00004000, 0x20404010, + 0x20400000, 0x00000010, 0x20404010, 0x00400000, + 0x20004000, 0x00404010, 0x00400000, 0x20000010, + 0x00400010, 0x20004000, 0x20000000, 0x00004010, + 0x00000000, 0x00400010, 0x20004010, 0x00004000, + 0x00404000, 0x20004010, 0x00000010, 0x20400010, + 0x20400010, 0x00000000, 0x00404010, 0x20404000, + 0x00004010, 0x00404000, 0x20404000, 0x20000000, + 0x20004000, 0x00000010, 0x20400010, 0x00404000, + 0x20404010, 0x00400000, 0x00004010, 0x20000010, + 0x00400000, 0x20004000, 0x20000000, 0x00004010, + 0x20000010, 0x20404010, 0x00404000, 0x20400000, + 0x00404010, 0x20404000, 0x00000000, 0x20400010, + 0x00000010, 0x00004000, 0x20400000, 0x00404010, + 0x00004000, 0x00400010, 0x20004010, 0x00000000, + 0x20404000, 0x20000000, 0x00400010, 0x20004010 + }; + + private static final int[] SP7 = { + 0x00200000, 0x04200002, 0x04000802, 0x00000000, + 0x00000800, 0x04000802, 0x00200802, 0x04200800, + 0x04200802, 0x00200000, 0x00000000, 0x04000002, + 0x00000002, 0x04000000, 0x04200002, 0x00000802, + 0x04000800, 0x00200802, 0x00200002, 0x04000800, + 0x04000002, 0x04200000, 0x04200800, 0x00200002, + 0x04200000, 0x00000800, 0x00000802, 0x04200802, + 0x00200800, 0x00000002, 0x04000000, 0x00200800, + 0x04000000, 0x00200800, 0x00200000, 0x04000802, + 0x04000802, 0x04200002, 0x04200002, 0x00000002, + 0x00200002, 0x04000000, 0x04000800, 0x00200000, + 0x04200800, 0x00000802, 0x00200802, 0x04200800, + 0x00000802, 0x04000002, 0x04200802, 0x04200000, + 0x00200800, 0x00000000, 0x00000002, 0x04200802, + 0x00000000, 0x00200802, 0x04200000, 0x00000800, + 0x04000002, 0x04000800, 0x00000800, 0x00200002 + }; + + private static final int[] SP8 = { + 0x10001040, 0x00001000, 0x00040000, 0x10041040, + 0x10000000, 0x10001040, 0x00000040, 0x10000000, + 0x00040040, 0x10040000, 0x10041040, 0x00041000, + 0x10041000, 0x00041040, 0x00001000, 0x00000040, + 0x10040000, 0x10000040, 0x10001000, 0x00001040, + 0x00041000, 0x00040040, 0x10040040, 0x10041000, + 0x00001040, 0x00000000, 0x00000000, 0x10040040, + 0x10000040, 0x10001000, 0x00041040, 0x00040000, + 0x00041040, 0x00040000, 0x10041000, 0x00001000, + 0x00000040, 0x10040040, 0x00001000, 0x00041040, + 0x10001000, 0x00000040, 0x10000040, 0x10040000, + 0x10040040, 0x10000000, 0x00040000, 0x10001040, + 0x00000000, 0x10041040, 0x00040040, 0x10000040, + 0x10040000, 0x10001000, 0x10001040, 0x00000000, + 0x10041040, 0x00041000, 0x00041000, 0x00001040, + 0x00001040, 0x00040040, 0x10000000, 0x10041000 + }; + + /** + * generate an integer based working key based on our secret key + * and what we processing we are planning to do. + * + * Acknowledgements for this routine go to James Gillogly & Phil Karn. + * (whoever, and wherever they are!). + */ + protected int[] generateWorkingKey( + boolean encrypting, + byte[] key) + { + int[] newKey = new int[32]; + boolean[] pc1m = new boolean[56], + pcr = new boolean[56]; + + for (int j = 0; j < 56; j++) + { + int l = pc1[j]; + + pc1m[j] = ((key[l >>> 3] & bytebit[l & 07]) != 0); + } + + for (int i = 0; i < 16; i++) + { + int l, m, n; + + if (encrypting) + { + m = i << 1; + } + else + { + m = (15 - i) << 1; + } + + n = m + 1; + newKey[m] = newKey[n] = 0; + + for (int j = 0; j < 28; j++) + { + l = j + totrot[i]; + if (l < 28) + { + pcr[j] = pc1m[l]; + } + else + { + pcr[j] = pc1m[l - 28]; + } + } + + for (int j = 28; j < 56; j++) + { + l = j + totrot[i]; + if (l < 56) + { + pcr[j] = pc1m[l]; + } + else + { + pcr[j] = pc1m[l - 28]; + } + } + + for (int j = 0; j < 24; j++) + { + if (pcr[pc2[j]]) + { + newKey[m] |= bigbyte[j]; + } + + if (pcr[pc2[j + 24]]) + { + newKey[n] |= bigbyte[j]; + } + } + } + + // + // store the processed key + // + for (int i = 0; i != 32; i += 2) + { + int i1, i2; + + i1 = newKey[i]; + i2 = newKey[i + 1]; + + newKey[i] = ((i1 & 0x00fc0000) << 6) | ((i1 & 0x00000fc0) << 10) + | ((i2 & 0x00fc0000) >>> 10) | ((i2 & 0x00000fc0) >>> 6); + + newKey[i + 1] = ((i1 & 0x0003f000) << 12) | ((i1 & 0x0000003f) << 16) + | ((i2 & 0x0003f000) >>> 4) | (i2 & 0x0000003f); + } + + return newKey; + } + + /** + * the DES engine. + */ + protected void desFunc( + int[] wKey, + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int work, right, left; + + left = (in[inOff + 0] & 0xff) << 24; + left |= (in[inOff + 1] & 0xff) << 16; + left |= (in[inOff + 2] & 0xff) << 8; + left |= (in[inOff + 3] & 0xff); + + right = (in[inOff + 4] & 0xff) << 24; + right |= (in[inOff + 5] & 0xff) << 16; + right |= (in[inOff + 6] & 0xff) << 8; + right |= (in[inOff + 7] & 0xff); + + work = ((left >>> 4) ^ right) & 0x0f0f0f0f; + right ^= work; + left ^= (work << 4); + work = ((left >>> 16) ^ right) & 0x0000ffff; + right ^= work; + left ^= (work << 16); + work = ((right >>> 2) ^ left) & 0x33333333; + left ^= work; + right ^= (work << 2); + work = ((right >>> 8) ^ left) & 0x00ff00ff; + left ^= work; + right ^= (work << 8); + right = ((right << 1) | ((right >>> 31) & 1)) & 0xffffffff; + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = ((left << 1) | ((left >>> 31) & 1)) & 0xffffffff; + + for (int round = 0; round < 8; round++) + { + int fval; + + work = (right << 28) | (right >>> 4); + work ^= wKey[round * 4 + 0]; + fval = SP7[ work & 0x3f]; + fval |= SP5[(work >>> 8) & 0x3f]; + fval |= SP3[(work >>> 16) & 0x3f]; + fval |= SP1[(work >>> 24) & 0x3f]; + work = right ^ wKey[round * 4 + 1]; + fval |= SP8[ work & 0x3f]; + fval |= SP6[(work >>> 8) & 0x3f]; + fval |= SP4[(work >>> 16) & 0x3f]; + fval |= SP2[(work >>> 24) & 0x3f]; + left ^= fval; + work = (left << 28) | (left >>> 4); + work ^= wKey[round * 4 + 2]; + fval = SP7[ work & 0x3f]; + fval |= SP5[(work >>> 8) & 0x3f]; + fval |= SP3[(work >>> 16) & 0x3f]; + fval |= SP1[(work >>> 24) & 0x3f]; + work = left ^ wKey[round * 4 + 3]; + fval |= SP8[ work & 0x3f]; + fval |= SP6[(work >>> 8) & 0x3f]; + fval |= SP4[(work >>> 16) & 0x3f]; + fval |= SP2[(work >>> 24) & 0x3f]; + right ^= fval; + } + + right = (right << 31) | (right >>> 1); + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = (left << 31) | (left >>> 1); + work = ((left >>> 8) ^ right) & 0x00ff00ff; + right ^= work; + left ^= (work << 8); + work = ((left >>> 2) ^ right) & 0x33333333; + right ^= work; + left ^= (work << 2); + work = ((right >>> 16) ^ left) & 0x0000ffff; + left ^= work; + right ^= (work << 16); + work = ((right >>> 4) ^ left) & 0x0f0f0f0f; + left ^= work; + right ^= (work << 4); + + out[outOff + 0] = (byte)((right >>> 24) & 0xff); + out[outOff + 1] = (byte)((right >>> 16) & 0xff); + out[outOff + 2] = (byte)((right >>> 8) & 0xff); + out[outOff + 3] = (byte)(right & 0xff); + out[outOff + 4] = (byte)((left >>> 24) & 0xff); + out[outOff + 5] = (byte)((left >>> 16) & 0xff); + out[outOff + 6] = (byte)((left >>> 8) & 0xff); + out[outOff + 7] = (byte)(left & 0xff); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/DESedeEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/DESedeEngine.java new file mode 100644 index 000000000..292c56219 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/DESedeEngine.java @@ -0,0 +1,126 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * a class that provides a basic DESede (or Triple DES) engine. + */ +public class DESedeEngine + extends DESEngine +{ + protected static final int BLOCK_SIZE = 8; + + private int[] workingKey1 = null; + private int[] workingKey2 = null; + private int[] workingKey3 = null; + + private boolean forEncryption; + + /** + * standard constructor. + */ + public DESedeEngine() + { + } + + /** + * initialise a DESede cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("invalid parameter passed to DESede init - " + params.getClass().getName()); + } + + byte[] keyMaster = ((KeyParameter)params).getKey(); + + if (keyMaster.length > 24) + { + throw new IllegalArgumentException("key size greater than 24 bytes"); + } + + this.forEncryption = encrypting; + + byte[] key1 = new byte[8]; + System.arraycopy(keyMaster, 0, key1, 0, key1.length); + workingKey1 = generateWorkingKey(encrypting, key1); + + byte[] key2 = new byte[8]; + System.arraycopy(keyMaster, 8, key2, 0, key2.length); + workingKey2 = generateWorkingKey(!encrypting, key2); + + if (keyMaster.length == 24) + { + byte[] key3 = new byte[8]; + System.arraycopy(keyMaster, 16, key3, 0, key3.length); + workingKey3 = generateWorkingKey(encrypting, key3); + } + else // 16 byte key + { + workingKey3 = workingKey1; + } + } + + public String getAlgorithmName() + { + return "DESede"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey1 == null) + { + throw new IllegalStateException("DESede engine not initialised"); + } + + if ((inOff + BLOCK_SIZE) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + BLOCK_SIZE) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + byte[] temp = new byte[BLOCK_SIZE]; + + if (forEncryption) + { + desFunc(workingKey1, in, inOff, temp, 0); + desFunc(workingKey2, temp, 0, temp, 0); + desFunc(workingKey3, temp, 0, out, outOff); + } + else + { + desFunc(workingKey3, in, inOff, temp, 0); + desFunc(workingKey2, temp, 0, temp, 0); + desFunc(workingKey1, temp, 0, out, outOff); + } + + return BLOCK_SIZE; + } + + public void reset() + { + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/DESedeWrapEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/DESedeWrapEngine.java new file mode 100644 index 000000000..03078981d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/DESedeWrapEngine.java @@ -0,0 +1,348 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; +import com.google.bitcoin.bouncycastle.crypto.Wrapper; +import com.google.bitcoin.bouncycastle.crypto.digests.SHA1Digest; +import com.google.bitcoin.bouncycastle.crypto.modes.CBCBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.util.Arrays; + +/** + * Wrap keys according to + * + * draft-ietf-smime-key-wrap-01.txt. + *

+ * Note: + *

    + *
  • this is based on a draft, and as such is subject to change - don't use this class for anything requiring long term storage. + *
  • if you are using this to wrap triple-des keys you need to set the + * parity bits on the key and, if it's a two-key triple-des key, pad it + * yourself. + *
+ */ +public class DESedeWrapEngine + implements Wrapper +{ + /** Field engine */ + private CBCBlockCipher engine; + + /** Field param */ + private KeyParameter param; + + /** Field paramPlusIV */ + private ParametersWithIV paramPlusIV; + + /** Field iv */ + private byte[] iv; + + /** Field forWrapping */ + private boolean forWrapping; + + /** Field IV2 */ + private static final byte[] IV2 = { (byte) 0x4a, (byte) 0xdd, (byte) 0xa2, + (byte) 0x2c, (byte) 0x79, (byte) 0xe8, + (byte) 0x21, (byte) 0x05 }; + + // + // checksum digest + // + Digest sha1 = new SHA1Digest(); + byte[] digest = new byte[20]; + + /** + * Method init + * + * @param forWrapping + * @param param + */ + public void init(boolean forWrapping, CipherParameters param) + { + + this.forWrapping = forWrapping; + this.engine = new CBCBlockCipher(new DESedeEngine()); + + SecureRandom sr; + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom pr = (ParametersWithRandom) param; + param = pr.getParameters(); + sr = pr.getRandom(); + } + else + { + sr = new SecureRandom(); + } + + if (param instanceof KeyParameter) + { + this.param = (KeyParameter)param; + + if (this.forWrapping) + { + + // Hm, we have no IV but we want to wrap ?!? + // well, then we have to create our own IV. + this.iv = new byte[8]; + sr.nextBytes(iv); + + this.paramPlusIV = new ParametersWithIV(this.param, this.iv); + } + } + else if (param instanceof ParametersWithIV) + { + this.paramPlusIV = (ParametersWithIV)param; + this.iv = this.paramPlusIV.getIV(); + this.param = (KeyParameter)this.paramPlusIV.getParameters(); + + if (this.forWrapping) + { + if ((this.iv == null) || (this.iv.length != 8)) + { + throw new IllegalArgumentException("IV is not 8 octets"); + } + } + else + { + throw new IllegalArgumentException( + "You should not supply an IV for unwrapping"); + } + } + } + + /** + * Method getAlgorithmName + * + * @return the algorithm name "DESede". + */ + public String getAlgorithmName() + { + return "DESede"; + } + + /** + * Method wrap + * + * @param in + * @param inOff + * @param inLen + * @return the wrapped bytes. + */ + public byte[] wrap(byte[] in, int inOff, int inLen) + { + if (!forWrapping) + { + throw new IllegalStateException("Not initialized for wrapping"); + } + + byte keyToBeWrapped[] = new byte[inLen]; + + System.arraycopy(in, inOff, keyToBeWrapped, 0, inLen); + + // Compute the CMS Key Checksum, (section 5.6.1), call this CKS. + byte[] CKS = calculateCMSKeyChecksum(keyToBeWrapped); + + // Let WKCKS = WK || CKS where || is concatenation. + byte[] WKCKS = new byte[keyToBeWrapped.length + CKS.length]; + + System.arraycopy(keyToBeWrapped, 0, WKCKS, 0, keyToBeWrapped.length); + System.arraycopy(CKS, 0, WKCKS, keyToBeWrapped.length, CKS.length); + + // Encrypt WKCKS in CBC mode using KEK as the key and IV as the + // initialization vector. Call the results TEMP1. + + int blockSize = engine.getBlockSize(); + + if (WKCKS.length % blockSize != 0) + { + throw new IllegalStateException("Not multiple of block length"); + } + + engine.init(true, paramPlusIV); + + byte TEMP1[] = new byte[WKCKS.length]; + + for (int currentBytePos = 0; currentBytePos != WKCKS.length; currentBytePos += blockSize) + { + engine.processBlock(WKCKS, currentBytePos, TEMP1, currentBytePos); + } + + // Let TEMP2 = IV || TEMP1. + byte[] TEMP2 = new byte[this.iv.length + TEMP1.length]; + + System.arraycopy(this.iv, 0, TEMP2, 0, this.iv.length); + System.arraycopy(TEMP1, 0, TEMP2, this.iv.length, TEMP1.length); + + // Reverse the order of the octets in TEMP2 and call the result TEMP3. + byte[] TEMP3 = reverse(TEMP2); + + // Encrypt TEMP3 in CBC mode using the KEK and an initialization vector + // of 0x 4a dd a2 2c 79 e8 21 05. The resulting cipher text is the desired + // result. It is 40 octets long if a 168 bit key is being wrapped. + ParametersWithIV param2 = new ParametersWithIV(this.param, IV2); + + this.engine.init(true, param2); + + for (int currentBytePos = 0; currentBytePos != TEMP3.length; currentBytePos += blockSize) + { + engine.processBlock(TEMP3, currentBytePos, TEMP3, currentBytePos); + } + + return TEMP3; + } + + /** + * Method unwrap + * + * @param in + * @param inOff + * @param inLen + * @return the unwrapped bytes. + * @throws InvalidCipherTextException + */ + public byte[] unwrap(byte[] in, int inOff, int inLen) + throws InvalidCipherTextException + { + if (forWrapping) + { + throw new IllegalStateException("Not set for unwrapping"); + } + + if (in == null) + { + throw new InvalidCipherTextException("Null pointer as ciphertext"); + } + + final int blockSize = engine.getBlockSize(); + if (inLen % blockSize != 0) + { + throw new InvalidCipherTextException("Ciphertext not multiple of " + blockSize); + } + + /* + // Check if the length of the cipher text is reasonable given the key + // type. It must be 40 bytes for a 168 bit key and either 32, 40, or + // 48 bytes for a 128, 192, or 256 bit key. If the length is not supported + // or inconsistent with the algorithm for which the key is intended, + // return error. + // + // we do not accept 168 bit keys. it has to be 192 bit. + int lengthA = (estimatedKeyLengthInBit / 8) + 16; + int lengthB = estimatedKeyLengthInBit % 8; + + if ((lengthA != keyToBeUnwrapped.length) || (lengthB != 0)) { + throw new XMLSecurityException("empty"); + } + */ + + // Decrypt the cipher text with TRIPLedeS in CBC mode using the KEK + // and an initialization vector (IV) of 0x4adda22c79e82105. Call the output TEMP3. + ParametersWithIV param2 = new ParametersWithIV(this.param, IV2); + + this.engine.init(false, param2); + + byte TEMP3[] = new byte[inLen]; + + for (int currentBytePos = 0; currentBytePos != inLen; currentBytePos += blockSize) + { + engine.processBlock(in, inOff + currentBytePos, TEMP3, currentBytePos); + } + + // Reverse the order of the octets in TEMP3 and call the result TEMP2. + byte[] TEMP2 = reverse(TEMP3); + + // Decompose TEMP2 into IV, the first 8 octets, and TEMP1, the remaining octets. + this.iv = new byte[8]; + + byte[] TEMP1 = new byte[TEMP2.length - 8]; + + System.arraycopy(TEMP2, 0, this.iv, 0, 8); + System.arraycopy(TEMP2, 8, TEMP1, 0, TEMP2.length - 8); + + // Decrypt TEMP1 using TRIPLedeS in CBC mode using the KEK and the IV + // found in the previous step. Call the result WKCKS. + this.paramPlusIV = new ParametersWithIV(this.param, this.iv); + + this.engine.init(false, this.paramPlusIV); + + byte[] WKCKS = new byte[TEMP1.length]; + + for (int currentBytePos = 0; currentBytePos != WKCKS.length; currentBytePos += blockSize) + { + engine.processBlock(TEMP1, currentBytePos, WKCKS, currentBytePos); + } + + // Decompose WKCKS. CKS is the last 8 octets and WK, the wrapped key, are + // those octets before the CKS. + byte[] result = new byte[WKCKS.length - 8]; + byte[] CKStoBeVerified = new byte[8]; + + System.arraycopy(WKCKS, 0, result, 0, WKCKS.length - 8); + System.arraycopy(WKCKS, WKCKS.length - 8, CKStoBeVerified, 0, 8); + + // Calculate a CMS Key Checksum, (section 5.6.1), over the WK and compare + // with the CKS extracted in the above step. If they are not equal, return error. + if (!checkCMSKeyChecksum(result, CKStoBeVerified)) + { + throw new InvalidCipherTextException( + "Checksum inside ciphertext is corrupted"); + } + + // WK is the wrapped key, now extracted for use in data decryption. + return result; + } + + /** + * Some key wrap algorithms make use of the Key Checksum defined + * in CMS [CMS-Algorithms]. This is used to provide an integrity + * check value for the key being wrapped. The algorithm is + * + * - Compute the 20 octet SHA-1 hash on the key being wrapped. + * - Use the first 8 octets of this hash as the checksum value. + * + * @param key + * @return the CMS checksum. + * @throws RuntimeException + * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + */ + private byte[] calculateCMSKeyChecksum( + byte[] key) + { + byte[] result = new byte[8]; + + sha1.update(key, 0, key.length); + sha1.doFinal(digest, 0); + + System.arraycopy(digest, 0, result, 0, 8); + + return result; + } + + /** + * @param key + * @param checksum + * @return true if okay, false otherwise. + * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + */ + private boolean checkCMSKeyChecksum( + byte[] key, + byte[] checksum) + { + return Arrays.constantTimeAreEqual(calculateCMSKeyChecksum(key), checksum); + } + + private static byte[] reverse(byte[] bs) + { + byte[] result = new byte[bs.length]; + for (int i = 0; i < bs.length; i++) + { + result[i] = bs[bs.length - (i + 1)]; + } + return result; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/ElGamalEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/ElGamalEngine.java new file mode 100644 index 000000000..e7e4d50df --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/ElGamalEngine.java @@ -0,0 +1,217 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.ElGamalKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ElGamalPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ElGamalPublicKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.util.BigIntegers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * this does your basic ElGamal algorithm. + */ +public class ElGamalEngine + implements AsymmetricBlockCipher +{ + private ElGamalKeyParameters key; + private SecureRandom random; + private boolean forEncryption; + private int bitSize; + + private static final BigInteger ZERO = BigInteger.valueOf(0); + private static final BigInteger ONE = BigInteger.valueOf(1); + private static final BigInteger TWO = BigInteger.valueOf(2); + + /** + * initialise the ElGamal engine. + * + * @param forEncryption true if we are encrypting, false otherwise. + * @param param the necessary ElGamal key parameters. + */ + public void init( + boolean forEncryption, + CipherParameters param) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)param; + + this.key = (ElGamalKeyParameters)p.getParameters(); + this.random = p.getRandom(); + } + else + { + this.key = (ElGamalKeyParameters)param; + this.random = new SecureRandom(); + } + + this.forEncryption = forEncryption; + + BigInteger p = key.getParameters().getP(); + + bitSize = p.bitLength(); + + if (forEncryption) + { + if (!(key instanceof ElGamalPublicKeyParameters)) + { + throw new IllegalArgumentException("ElGamalPublicKeyParameters are required for encryption."); + } + } + else + { + if (!(key instanceof ElGamalPrivateKeyParameters)) + { + throw new IllegalArgumentException("ElGamalPrivateKeyParameters are required for decryption."); + } + } + } + + /** + * Return the maximum size for an input block to this engine. + * For ElGamal this is always one byte less than the size of P on + * encryption, and twice the length as the size of P on decryption. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize() + { + if (forEncryption) + { + return (bitSize - 1) / 8; + } + + return 2 * ((bitSize + 7) / 8); + } + + /** + * Return the maximum size for an output block to this engine. + * For ElGamal this is always one byte less than the size of P on + * decryption, and twice the length as the size of P on encryption. + * + * @return maximum size for an output block. + */ + public int getOutputBlockSize() + { + if (forEncryption) + { + return 2 * ((bitSize + 7) / 8); + } + + return (bitSize - 1) / 8; + } + + /** + * Process a single block using the basic ElGamal algorithm. + * + * @param in the input array. + * @param inOff the offset into the input buffer where the data starts. + * @param inLen the length of the data to be processed. + * @return the result of the ElGamal process. + * @exception DataLengthException the input block is too large. + */ + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + { + if (key == null) + { + throw new IllegalStateException("ElGamal engine not initialised"); + } + + int maxLength = forEncryption + ? (bitSize - 1 + 7) / 8 + : getInputBlockSize(); + + if (inLen > maxLength) + { + throw new DataLengthException("input too large for ElGamal cipher.\n"); + } + + BigInteger p = key.getParameters().getP(); + + if (key instanceof ElGamalPrivateKeyParameters) // decryption + { + byte[] in1 = new byte[inLen / 2]; + byte[] in2 = new byte[inLen / 2]; + + System.arraycopy(in, inOff, in1, 0, in1.length); + System.arraycopy(in, inOff + in1.length, in2, 0, in2.length); + + BigInteger gamma = new BigInteger(1, in1); + BigInteger phi = new BigInteger(1, in2); + + ElGamalPrivateKeyParameters priv = (ElGamalPrivateKeyParameters)key; + // a shortcut, which generally relies on p being prime amongst other things. + // if a problem with this shows up, check the p and g values! + BigInteger m = gamma.modPow(p.subtract(ONE).subtract(priv.getX()), p).multiply(phi).mod(p); + + return BigIntegers.asUnsignedByteArray(m); + } + else // encryption + { + byte[] block; + if (inOff != 0 || inLen != in.length) + { + block = new byte[inLen]; + + System.arraycopy(in, inOff, block, 0, inLen); + } + else + { + block = in; + } + + BigInteger input = new BigInteger(1, block); + + if (input.bitLength() >= p.bitLength()) + { + throw new DataLengthException("input too large for ElGamal cipher.\n"); + } + + ElGamalPublicKeyParameters pub = (ElGamalPublicKeyParameters)key; + + int pBitLength = p.bitLength(); + BigInteger k = new BigInteger(pBitLength, random); + + while (k.equals(ZERO) || (k.compareTo(p.subtract(TWO)) > 0)) + { + k = new BigInteger(pBitLength, random); + } + + BigInteger g = key.getParameters().getG(); + BigInteger gamma = g.modPow(k, p); + BigInteger phi = input.multiply(pub.getY().modPow(k, p)).mod(p); + + byte[] out1 = gamma.toByteArray(); + byte[] out2 = phi.toByteArray(); + byte[] output = new byte[this.getOutputBlockSize()]; + + if (out1.length > output.length / 2) + { + System.arraycopy(out1, 1, output, output.length / 2 - (out1.length - 1), out1.length - 1); + } + else + { + System.arraycopy(out1, 0, output, output.length / 2 - out1.length, out1.length); + } + + if (out2.length > output.length / 2) + { + System.arraycopy(out2, 1, output, output.length - (out2.length - 1), out2.length - 1); + } + else + { + System.arraycopy(out2, 0, output, output.length - out2.length, out2.length); + } + + return output; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/GOST28147Engine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/GOST28147Engine.java new file mode 100644 index 000000000..fd3add7fc --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/GOST28147Engine.java @@ -0,0 +1,363 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import java.util.Hashtable; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithSBox; +import com.google.bitcoin.bouncycastle.util.Strings; + +/** + * implementation of GOST 28147-89 + */ +public class GOST28147Engine + implements BlockCipher +{ + protected static final int BLOCK_SIZE = 8; + private int[] workingKey = null; + private boolean forEncryption; + + // these are the S-boxes given in Applied Cryptography 2nd Ed., p. 333 + // This is default S-box! + private byte S[] = { + 0x4,0xA,0x9,0x2,0xD,0x8,0x0,0xE,0x6,0xB,0x1,0xC,0x7,0xF,0x5,0x3, + 0xE,0xB,0x4,0xC,0x6,0xD,0xF,0xA,0x2,0x3,0x8,0x1,0x0,0x7,0x5,0x9, + 0x5,0x8,0x1,0xD,0xA,0x3,0x4,0x2,0xE,0xF,0xC,0x7,0x6,0x0,0x9,0xB, + 0x7,0xD,0xA,0x1,0x0,0x8,0x9,0xF,0xE,0x4,0x6,0xC,0xB,0x2,0x5,0x3, + 0x6,0xC,0x7,0x1,0x5,0xF,0xD,0x8,0x4,0xA,0x9,0xE,0x0,0x3,0xB,0x2, + 0x4,0xB,0xA,0x0,0x7,0x2,0x1,0xD,0x3,0x6,0x8,0x5,0x9,0xC,0xF,0xE, + 0xD,0xB,0x4,0x1,0x3,0xF,0x5,0x9,0x0,0xA,0xE,0x7,0x6,0x8,0x2,0xC, + 0x1,0xF,0xD,0x0,0x5,0x7,0xA,0x4,0x9,0x2,0x3,0xE,0x6,0xB,0x8,0xC + }; + + /* + * class content S-box parameters for encrypting + * getting from, see: http://www.ietf.org/internet-drafts/draft-popov-cryptopro-cpalgs-01.txt + * http://www.ietf.org/internet-drafts/draft-popov-cryptopro-cpalgs-02.txt + */ + private static byte[] ESbox_Test = { + 0x4,0x2,0xF,0x5,0x9,0x1,0x0,0x8,0xE,0x3,0xB,0xC,0xD,0x7,0xA,0x6, + 0xC,0x9,0xF,0xE,0x8,0x1,0x3,0xA,0x2,0x7,0x4,0xD,0x6,0x0,0xB,0x5, + 0xD,0x8,0xE,0xC,0x7,0x3,0x9,0xA,0x1,0x5,0x2,0x4,0x6,0xF,0x0,0xB, + 0xE,0x9,0xB,0x2,0x5,0xF,0x7,0x1,0x0,0xD,0xC,0x6,0xA,0x4,0x3,0x8, + 0x3,0xE,0x5,0x9,0x6,0x8,0x0,0xD,0xA,0xB,0x7,0xC,0x2,0x1,0xF,0x4, + 0x8,0xF,0x6,0xB,0x1,0x9,0xC,0x5,0xD,0x3,0x7,0xA,0x0,0xE,0x2,0x4, + 0x9,0xB,0xC,0x0,0x3,0x6,0x7,0x5,0x4,0x8,0xE,0xF,0x1,0xA,0x2,0xD, + 0xC,0x6,0x5,0x2,0xB,0x0,0x9,0xD,0x3,0xE,0x7,0xA,0xF,0x4,0x1,0x8 + }; + + private static byte[] ESbox_A = { + 0x9,0x6,0x3,0x2,0x8,0xB,0x1,0x7,0xA,0x4,0xE,0xF,0xC,0x0,0xD,0x5, + 0x3,0x7,0xE,0x9,0x8,0xA,0xF,0x0,0x5,0x2,0x6,0xC,0xB,0x4,0xD,0x1, + 0xE,0x4,0x6,0x2,0xB,0x3,0xD,0x8,0xC,0xF,0x5,0xA,0x0,0x7,0x1,0x9, + 0xE,0x7,0xA,0xC,0xD,0x1,0x3,0x9,0x0,0x2,0xB,0x4,0xF,0x8,0x5,0x6, + 0xB,0x5,0x1,0x9,0x8,0xD,0xF,0x0,0xE,0x4,0x2,0x3,0xC,0x7,0xA,0x6, + 0x3,0xA,0xD,0xC,0x1,0x2,0x0,0xB,0x7,0x5,0x9,0x4,0x8,0xF,0xE,0x6, + 0x1,0xD,0x2,0x9,0x7,0xA,0x6,0x0,0x8,0xC,0x4,0x5,0xF,0x3,0xB,0xE, + 0xB,0xA,0xF,0x5,0x0,0xC,0xE,0x8,0x6,0x2,0x3,0x9,0x1,0x7,0xD,0x4 + }; + + private static byte[] ESbox_B = { + 0x8,0x4,0xB,0x1,0x3,0x5,0x0,0x9,0x2,0xE,0xA,0xC,0xD,0x6,0x7,0xF, + 0x0,0x1,0x2,0xA,0x4,0xD,0x5,0xC,0x9,0x7,0x3,0xF,0xB,0x8,0x6,0xE, + 0xE,0xC,0x0,0xA,0x9,0x2,0xD,0xB,0x7,0x5,0x8,0xF,0x3,0x6,0x1,0x4, + 0x7,0x5,0x0,0xD,0xB,0x6,0x1,0x2,0x3,0xA,0xC,0xF,0x4,0xE,0x9,0x8, + 0x2,0x7,0xC,0xF,0x9,0x5,0xA,0xB,0x1,0x4,0x0,0xD,0x6,0x8,0xE,0x3, + 0x8,0x3,0x2,0x6,0x4,0xD,0xE,0xB,0xC,0x1,0x7,0xF,0xA,0x0,0x9,0x5, + 0x5,0x2,0xA,0xB,0x9,0x1,0xC,0x3,0x7,0x4,0xD,0x0,0x6,0xF,0x8,0xE, + 0x0,0x4,0xB,0xE,0x8,0x3,0x7,0x1,0xA,0x2,0x9,0x6,0xF,0xD,0x5,0xC + }; + + private static byte[] ESbox_C = { + 0x1,0xB,0xC,0x2,0x9,0xD,0x0,0xF,0x4,0x5,0x8,0xE,0xA,0x7,0x6,0x3, + 0x0,0x1,0x7,0xD,0xB,0x4,0x5,0x2,0x8,0xE,0xF,0xC,0x9,0xA,0x6,0x3, + 0x8,0x2,0x5,0x0,0x4,0x9,0xF,0xA,0x3,0x7,0xC,0xD,0x6,0xE,0x1,0xB, + 0x3,0x6,0x0,0x1,0x5,0xD,0xA,0x8,0xB,0x2,0x9,0x7,0xE,0xF,0xC,0x4, + 0x8,0xD,0xB,0x0,0x4,0x5,0x1,0x2,0x9,0x3,0xC,0xE,0x6,0xF,0xA,0x7, + 0xC,0x9,0xB,0x1,0x8,0xE,0x2,0x4,0x7,0x3,0x6,0x5,0xA,0x0,0xF,0xD, + 0xA,0x9,0x6,0x8,0xD,0xE,0x2,0x0,0xF,0x3,0x5,0xB,0x4,0x1,0xC,0x7, + 0x7,0x4,0x0,0x5,0xA,0x2,0xF,0xE,0xC,0x6,0x1,0xB,0xD,0x9,0x3,0x8 + }; + + private static byte[] ESbox_D = { + 0xF,0xC,0x2,0xA,0x6,0x4,0x5,0x0,0x7,0x9,0xE,0xD,0x1,0xB,0x8,0x3, + 0xB,0x6,0x3,0x4,0xC,0xF,0xE,0x2,0x7,0xD,0x8,0x0,0x5,0xA,0x9,0x1, + 0x1,0xC,0xB,0x0,0xF,0xE,0x6,0x5,0xA,0xD,0x4,0x8,0x9,0x3,0x7,0x2, + 0x1,0x5,0xE,0xC,0xA,0x7,0x0,0xD,0x6,0x2,0xB,0x4,0x9,0x3,0xF,0x8, + 0x0,0xC,0x8,0x9,0xD,0x2,0xA,0xB,0x7,0x3,0x6,0x5,0x4,0xE,0xF,0x1, + 0x8,0x0,0xF,0x3,0x2,0x5,0xE,0xB,0x1,0xA,0x4,0x7,0xC,0x9,0xD,0x6, + 0x3,0x0,0x6,0xF,0x1,0xE,0x9,0x2,0xD,0x8,0xC,0x4,0xB,0xA,0x5,0x7, + 0x1,0xA,0x6,0x8,0xF,0xB,0x0,0x4,0xC,0x3,0x5,0x9,0x7,0xD,0x2,0xE + }; + + //S-box for digest + private static byte DSbox_Test[] = { + 0x4,0xA,0x9,0x2,0xD,0x8,0x0,0xE,0x6,0xB,0x1,0xC,0x7,0xF,0x5,0x3, + 0xE,0xB,0x4,0xC,0x6,0xD,0xF,0xA,0x2,0x3,0x8,0x1,0x0,0x7,0x5,0x9, + 0x5,0x8,0x1,0xD,0xA,0x3,0x4,0x2,0xE,0xF,0xC,0x7,0x6,0x0,0x9,0xB, + 0x7,0xD,0xA,0x1,0x0,0x8,0x9,0xF,0xE,0x4,0x6,0xC,0xB,0x2,0x5,0x3, + 0x6,0xC,0x7,0x1,0x5,0xF,0xD,0x8,0x4,0xA,0x9,0xE,0x0,0x3,0xB,0x2, + 0x4,0xB,0xA,0x0,0x7,0x2,0x1,0xD,0x3,0x6,0x8,0x5,0x9,0xC,0xF,0xE, + 0xD,0xB,0x4,0x1,0x3,0xF,0x5,0x9,0x0,0xA,0xE,0x7,0x6,0x8,0x2,0xC, + 0x1,0xF,0xD,0x0,0x5,0x7,0xA,0x4,0x9,0x2,0x3,0xE,0x6,0xB,0x8,0xC + }; + + private static byte DSbox_A[] = { + 0xA,0x4,0x5,0x6,0x8,0x1,0x3,0x7,0xD,0xC,0xE,0x0,0x9,0x2,0xB,0xF, + 0x5,0xF,0x4,0x0,0x2,0xD,0xB,0x9,0x1,0x7,0x6,0x3,0xC,0xE,0xA,0x8, + 0x7,0xF,0xC,0xE,0x9,0x4,0x1,0x0,0x3,0xB,0x5,0x2,0x6,0xA,0x8,0xD, + 0x4,0xA,0x7,0xC,0x0,0xF,0x2,0x8,0xE,0x1,0x6,0x5,0xD,0xB,0x9,0x3, + 0x7,0x6,0x4,0xB,0x9,0xC,0x2,0xA,0x1,0x8,0x0,0xE,0xF,0xD,0x3,0x5, + 0x7,0x6,0x2,0x4,0xD,0x9,0xF,0x0,0xA,0x1,0x5,0xB,0x8,0xE,0xC,0x3, + 0xD,0xE,0x4,0x1,0x7,0x0,0x5,0xA,0x3,0xC,0x8,0xF,0x6,0x2,0x9,0xB, + 0x1,0x3,0xA,0x9,0x5,0xB,0x4,0xF,0x8,0x6,0x7,0xE,0xD,0x0,0x2,0xC + }; + + // + // pre-defined sbox table + // + private static Hashtable sBoxes = new Hashtable(); + + static + { + sBoxes.put("E-TEST", ESbox_Test); + sBoxes.put("E-A", ESbox_A); + sBoxes.put("E-B", ESbox_B); + sBoxes.put("E-C", ESbox_C); + sBoxes.put("E-D", ESbox_D); + sBoxes.put("D-TEST", DSbox_Test); + sBoxes.put("D-A", DSbox_A); + } + + /** + * standard constructor. + */ + public GOST28147Engine() + { + } + + /** + * initialise an GOST28147 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (params instanceof ParametersWithSBox) + { + ParametersWithSBox param = (ParametersWithSBox)params; + + // + // Set the S-Box + // + System.arraycopy(param.getSBox(), 0, this.S, 0, param.getSBox().length); + + // + // set key if there is one + // + if (param.getParameters() != null) + { + workingKey = generateWorkingKey(forEncryption, + ((KeyParameter)param.getParameters()).getKey()); + } + } + else if (params instanceof KeyParameter) + { + workingKey = generateWorkingKey(forEncryption, + ((KeyParameter)params).getKey()); + } + else + { + throw new IllegalArgumentException("invalid parameter passed to GOST28147 init - " + params.getClass().getName()); + } + } + + public String getAlgorithmName() + { + return "GOST28147"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("GOST28147 engine not initialised"); + } + + if ((inOff + BLOCK_SIZE) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + BLOCK_SIZE) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + GOST28147Func(workingKey, in, inOff, out, outOff); + + return BLOCK_SIZE; + } + + public void reset() + { + } + + private int[] generateWorkingKey( + boolean forEncryption, + byte[] userKey) + { + this.forEncryption = forEncryption; + + if (userKey.length != 32) + { + throw new IllegalArgumentException("Key length invalid. Key needs to be 32 byte - 256 bit!!!"); + } + + int key[] = new int[8]; + for(int i=0; i!=8; i++) + { + key[i] = bytesToint(userKey,i*4); + } + + return key; + } + + private int GOST28147_mainStep(int n1, int key) + { + int cm = (key + n1); // CM1 + + // S-box replacing + + int om = S[ 0 + ((cm >> (0 * 4)) & 0xF)] << (0 * 4); + om += S[ 16 + ((cm >> (1 * 4)) & 0xF)] << (1 * 4); + om += S[ 32 + ((cm >> (2 * 4)) & 0xF)] << (2 * 4); + om += S[ 48 + ((cm >> (3 * 4)) & 0xF)] << (3 * 4); + om += S[ 64 + ((cm >> (4 * 4)) & 0xF)] << (4 * 4); + om += S[ 80 + ((cm >> (5 * 4)) & 0xF)] << (5 * 4); + om += S[ 96 + ((cm >> (6 * 4)) & 0xF)] << (6 * 4); + om += S[112 + ((cm >> (7 * 4)) & 0xF)] << (7 * 4); + + return om << 11 | om >>> (32-11); // 11-leftshift + } + + private void GOST28147Func( + int[] workingKey, + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int N1, N2, tmp; //tmp -> for saving N1 + N1 = bytesToint(in, inOff); + N2 = bytesToint(in, inOff + 4); + + if (this.forEncryption) + { + for(int k = 0; k < 3; k++) // 1-24 steps + { + for(int j = 0; j < 8; j++) + { + tmp = N1; + N1 = N2 ^ GOST28147_mainStep(N1, workingKey[j]); // CM2 + N2 = tmp; + } + } + for(int j = 7; j > 0; j--) // 25-31 steps + { + tmp = N1; + N1 = N2 ^ GOST28147_mainStep(N1, workingKey[j]); // CM2 + N2 = tmp; + } + } + else //decrypt + { + for(int j = 0; j < 8; j++) // 1-8 steps + { + tmp = N1; + N1 = N2 ^ GOST28147_mainStep(N1, workingKey[j]); // CM2 + N2 = tmp; + } + for(int k = 0; k < 3; k++) //9-31 steps + { + for(int j = 7; j >= 0; j--) + { + if ((k == 2) && (j==0)) + { + break; // break 32 step + } + tmp = N1; + N1 = N2 ^ GOST28147_mainStep(N1, workingKey[j]); // CM2 + N2 = tmp; + } + } + } + + N2 = N2 ^ GOST28147_mainStep(N1, workingKey[0]); // 32 step (N1=N1) + + intTobytes(N1, out, outOff); + intTobytes(N2, out, outOff + 4); + } + + //array of bytes to type int + private int bytesToint( + byte[] in, + int inOff) + { + return ((in[inOff + 3] << 24) & 0xff000000) + ((in[inOff + 2] << 16) & 0xff0000) + + ((in[inOff + 1] << 8) & 0xff00) + (in[inOff] & 0xff); + } + + //int to array of bytes + private void intTobytes( + int num, + byte[] out, + int outOff) + { + out[outOff + 3] = (byte)(num >>> 24); + out[outOff + 2] = (byte)(num >>> 16); + out[outOff + 1] = (byte)(num >>> 8); + out[outOff] = (byte)num; + } + + /** + * Return the S-Box associated with SBoxName + * @param sBoxName name of the S-Box + * @return byte array representing the S-Box + */ + public static byte[] getSBox( + String sBoxName) + { + byte[] namedSBox = (byte[])sBoxes.get(Strings.toUpperCase(sBoxName)); + + if (namedSBox != null) + { + byte[] sBox = new byte[namedSBox.length]; + + System.arraycopy(namedSBox, 0, sBox, 0, sBox.length); + + return sBox; + } + else + { + throw new IllegalArgumentException("Unknown S-Box - possible types: " + + "\"E-Test\", \"E-A\", \"E-B\", \"E-C\", \"E-D\", \"D-Test\", \"D-A\"."); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/Grain128Engine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/Grain128Engine.java new file mode 100644 index 000000000..8dcd16970 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/Grain128Engine.java @@ -0,0 +1,302 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.StreamCipher; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Implementation of Martin Hell's, Thomas Johansson's and Willi Meier's stream + * cipher, Grain-128. + */ +public class Grain128Engine + implements StreamCipher +{ + + /** + * Constants + */ + private static final int STATE_SIZE = 4; + + /** + * Variables to hold the state of the engine during encryption and + * decryption + */ + private byte[] workingKey; + private byte[] workingIV; + private byte[] out; + private int[] lfsr; + private int[] nfsr; + private int output; + private int index = 4; + + private boolean initialised = false; + + public String getAlgorithmName() + { + return "Grain-128"; + } + + /** + * Initialize a Grain-128 cipher. + * + * @param forEncryption Whether or not we are for encryption. + * @param params The parameters required to set up the cipher. + * @throws IllegalArgumentException If the params argument is inappropriate. + */ + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + /** + * Grain encryption and decryption is completely symmetrical, so the + * 'forEncryption' is irrelevant. + */ + if (!(params instanceof ParametersWithIV)) + { + throw new IllegalArgumentException( + "Grain-128 Init parameters must include an IV"); + } + + ParametersWithIV ivParams = (ParametersWithIV)params; + + byte[] iv = ivParams.getIV(); + + if (iv == null || iv.length != 12) + { + throw new IllegalArgumentException( + "Grain-128 requires exactly 12 bytes of IV"); + } + + if (!(ivParams.getParameters() instanceof KeyParameter)) + { + throw new IllegalArgumentException( + "Grain-128 Init parameters must include a key"); + } + + KeyParameter key = (KeyParameter)ivParams.getParameters(); + + /** + * Initialize variables. + */ + workingIV = new byte[key.getKey().length]; + workingKey = new byte[key.getKey().length]; + lfsr = new int[STATE_SIZE]; + nfsr = new int[STATE_SIZE]; + out = new byte[4]; + + System.arraycopy(iv, 0, workingIV, 0, iv.length); + System.arraycopy(key.getKey(), 0, workingKey, 0, key.getKey().length); + + setKey(workingKey, workingIV); + initGrain(); + } + + /** + * 256 clocks initialization phase. + */ + private void initGrain() + { + for (int i = 0; i < 8; i++) + { + output = getOutput(); + nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0] ^ output); + lfsr = shift(lfsr, getOutputLFSR() ^ output); + } + initialised = true; + } + + /** + * Get output from non-linear function g(x). + * + * @return Output from NFSR. + */ + private int getOutputNFSR() + { + int b0 = nfsr[0]; + int b3 = nfsr[0] >>> 3 | nfsr[1] << 29; + int b11 = nfsr[0] >>> 11 | nfsr[1] << 21; + int b13 = nfsr[0] >>> 13 | nfsr[1] << 19; + int b17 = nfsr[0] >>> 17 | nfsr[1] << 15; + int b18 = nfsr[0] >>> 18 | nfsr[1] << 14; + int b26 = nfsr[0] >>> 26 | nfsr[1] << 6; + int b27 = nfsr[0] >>> 27 | nfsr[1] << 5; + int b40 = nfsr[1] >>> 8 | nfsr[2] << 24; + int b48 = nfsr[1] >>> 16 | nfsr[2] << 16; + int b56 = nfsr[1] >>> 24 | nfsr[2] << 8; + int b59 = nfsr[1] >>> 27 | nfsr[2] << 5; + int b61 = nfsr[1] >>> 29 | nfsr[2] << 3; + int b65 = nfsr[2] >>> 1 | nfsr[3] << 31; + int b67 = nfsr[2] >>> 3 | nfsr[3] << 29; + int b68 = nfsr[2] >>> 4 | nfsr[3] << 28; + int b84 = nfsr[2] >>> 20 | nfsr[3] << 12; + int b91 = nfsr[2] >>> 27 | nfsr[3] << 5; + int b96 = nfsr[3]; + + return b0 ^ b26 ^ b56 ^ b91 ^ b96 ^ b3 & b67 ^ b11 & b13 ^ b17 & b18 + ^ b27 & b59 ^ b40 & b48 ^ b61 & b65 ^ b68 & b84; + } + + /** + * Get output from linear function f(x). + * + * @return Output from LFSR. + */ + private int getOutputLFSR() + { + int s0 = lfsr[0]; + int s7 = lfsr[0] >>> 7 | lfsr[1] << 25; + int s38 = lfsr[1] >>> 6 | lfsr[2] << 26; + int s70 = lfsr[2] >>> 6 | lfsr[3] << 26; + int s81 = lfsr[2] >>> 17 | lfsr[3] << 15; + int s96 = lfsr[3]; + + return s0 ^ s7 ^ s38 ^ s70 ^ s81 ^ s96; + } + + /** + * Get output from output function h(x). + * + * @return Output from h(x). + */ + private int getOutput() + { + int b2 = nfsr[0] >>> 2 | nfsr[1] << 30; + int b12 = nfsr[0] >>> 12 | nfsr[1] << 20; + int b15 = nfsr[0] >>> 15 | nfsr[1] << 17; + int b36 = nfsr[1] >>> 4 | nfsr[2] << 28; + int b45 = nfsr[1] >>> 13 | nfsr[2] << 19; + int b64 = nfsr[2]; + int b73 = nfsr[2] >>> 9 | nfsr[3] << 23; + int b89 = nfsr[2] >>> 25 | nfsr[3] << 7; + int b95 = nfsr[2] >>> 31 | nfsr[3] << 1; + int s8 = lfsr[0] >>> 8 | lfsr[1] << 24; + int s13 = lfsr[0] >>> 13 | lfsr[1] << 19; + int s20 = lfsr[0] >>> 20 | lfsr[1] << 12; + int s42 = lfsr[1] >>> 10 | lfsr[2] << 22; + int s60 = lfsr[1] >>> 28 | lfsr[2] << 4; + int s79 = lfsr[2] >>> 15 | lfsr[3] << 17; + int s93 = lfsr[2] >>> 29 | lfsr[3] << 3; + int s95 = lfsr[2] >>> 31 | lfsr[3] << 1; + + return b12 & s8 ^ s13 & s20 ^ b95 & s42 ^ s60 & s79 ^ b12 & b95 & s95 ^ s93 + ^ b2 ^ b15 ^ b36 ^ b45 ^ b64 ^ b73 ^ b89; + } + + /** + * Shift array 32 bits and add val to index.length - 1. + * + * @param array The array to shift. + * @param val The value to shift in. + * @return The shifted array with val added to index.length - 1. + */ + private int[] shift(int[] array, int val) + { + array[0] = array[1]; + array[1] = array[2]; + array[2] = array[3]; + array[3] = val; + + return array; + } + + /** + * Set keys, reset cipher. + * + * @param keyBytes The key. + * @param ivBytes The IV. + */ + private void setKey(byte[] keyBytes, byte[] ivBytes) + { + ivBytes[12] = (byte)0xFF; + ivBytes[13] = (byte)0xFF; + ivBytes[14] = (byte)0xFF; + ivBytes[15] = (byte)0xFF; + workingKey = keyBytes; + workingIV = ivBytes; + + /** + * Load NFSR and LFSR + */ + int j = 0; + for (int i = 0; i < nfsr.length; i++) + { + nfsr[i] = ((workingKey[j + 3]) << 24) | ((workingKey[j + 2]) << 16) + & 0x00FF0000 | ((workingKey[j + 1]) << 8) & 0x0000FF00 + | ((workingKey[j]) & 0x000000FF); + + lfsr[i] = ((workingIV[j + 3]) << 24) | ((workingIV[j + 2]) << 16) + & 0x00FF0000 | ((workingIV[j + 1]) << 8) & 0x0000FF00 + | ((workingIV[j]) & 0x000000FF); + j += 4; + } + } + + public void processBytes(byte[] in, int inOff, int len, byte[] out, + int outOff) + throws DataLengthException + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName() + + " not initialised"); + } + + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + for (int i = 0; i < len; i++) + { + out[outOff + i] = (byte)(in[inOff + i] ^ getKeyStream()); + } + } + + public void reset() + { + index = 4; + setKey(workingKey, workingIV); + initGrain(); + } + + /** + * Run Grain one round(i.e. 32 bits). + */ + private void oneRound() + { + output = getOutput(); + out[0] = (byte)output; + out[1] = (byte)(output >> 8); + out[2] = (byte)(output >> 16); + out[3] = (byte)(output >> 24); + + nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0]); + lfsr = shift(lfsr, getOutputLFSR()); + } + + public byte returnByte(byte in) + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName() + + " not initialised"); + } + return (byte)(in ^ getKeyStream()); + } + + private byte getKeyStream() + { + if (index > 3) + { + oneRound(); + index = 0; + } + return out[index++]; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/Grainv1Engine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/Grainv1Engine.java new file mode 100644 index 000000000..4e5bc941e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/Grainv1Engine.java @@ -0,0 +1,288 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.StreamCipher; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Implementation of Martin Hell's, Thomas Johansson's and Willi Meier's stream + * cipher, Grain v1. + */ +public class Grainv1Engine + implements StreamCipher +{ + + /** + * Constants + */ + private static final int STATE_SIZE = 5; + + /** + * Variables to hold the state of the engine during encryption and + * decryption + */ + private byte[] workingKey; + private byte[] workingIV; + private byte[] out; + private int[] lfsr; + private int[] nfsr; + private int output; + private int index = 2; + + private boolean initialised = false; + + public String getAlgorithmName() + { + return "Grain v1"; + } + + /** + * Initialize a Grain v1 cipher. + * + * @param forEncryption Whether or not we are for encryption. + * @param params The parameters required to set up the cipher. + * @throws IllegalArgumentException If the params argument is inappropriate. + */ + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + /** + * Grain encryption and decryption is completely symmetrical, so the + * 'forEncryption' is irrelevant. + */ + if (!(params instanceof ParametersWithIV)) + { + throw new IllegalArgumentException( + "Grain v1 Init parameters must include an IV"); + } + + ParametersWithIV ivParams = (ParametersWithIV)params; + + byte[] iv = ivParams.getIV(); + + if (iv == null || iv.length != 8) + { + throw new IllegalArgumentException( + "Grain v1 requires exactly 8 bytes of IV"); + } + + if (!(ivParams.getParameters() instanceof KeyParameter)) + { + throw new IllegalArgumentException( + "Grain v1 Init parameters must include a key"); + } + + KeyParameter key = (KeyParameter)ivParams.getParameters(); + + /** + * Initialize variables. + */ + workingIV = new byte[key.getKey().length]; + workingKey = new byte[key.getKey().length]; + lfsr = new int[STATE_SIZE]; + nfsr = new int[STATE_SIZE]; + out = new byte[2]; + + System.arraycopy(iv, 0, workingIV, 0, iv.length); + System.arraycopy(key.getKey(), 0, workingKey, 0, key.getKey().length); + + setKey(workingKey, workingIV); + initGrain(); + } + + /** + * 160 clocks initialization phase. + */ + private void initGrain() + { + for (int i = 0; i < 10; i++) + { + output = getOutput(); + nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0] ^ output); + lfsr = shift(lfsr, getOutputLFSR() ^ output); + } + initialised = true; + } + + /** + * Get output from non-linear function g(x). + * + * @return Output from NFSR. + */ + private int getOutputNFSR() + { + int b0 = nfsr[0]; + int b9 = nfsr[0] >>> 9 | nfsr[1] << 7; + int b14 = nfsr[0] >>> 14 | nfsr[1] << 2; + int b15 = nfsr[0] >>> 15 | nfsr[1] << 1; + int b21 = nfsr[1] >>> 5 | nfsr[2] << 11; + int b28 = nfsr[1] >>> 12 | nfsr[2] << 4; + int b33 = nfsr[2] >>> 1 | nfsr[3] << 15; + int b37 = nfsr[2] >>> 5 | nfsr[3] << 11; + int b45 = nfsr[2] >>> 13 | nfsr[3] << 3; + int b52 = nfsr[3] >>> 4 | nfsr[4] << 12; + int b60 = nfsr[3] >>> 12 | nfsr[4] << 4; + int b62 = nfsr[3] >>> 14 | nfsr[4] << 2; + int b63 = nfsr[3] >>> 15 | nfsr[4] << 1; + + return (b62 ^ b60 ^ b52 ^ b45 ^ b37 ^ b33 ^ b28 ^ b21 ^ b14 + ^ b9 ^ b0 ^ b63 & b60 ^ b37 & b33 ^ b15 & b9 ^ b60 & b52 & b45 + ^ b33 & b28 & b21 ^ b63 & b45 & b28 & b9 ^ b60 & b52 & b37 + & b33 ^ b63 & b60 & b21 & b15 ^ b63 & b60 & b52 & b45 & b37 + ^ b33 & b28 & b21 & b15 & b9 ^ b52 & b45 & b37 & b33 & b28 + & b21) & 0x0000FFFF; + } + + /** + * Get output from linear function f(x). + * + * @return Output from LFSR. + */ + private int getOutputLFSR() + { + int s0 = lfsr[0]; + int s13 = lfsr[0] >>> 13 | lfsr[1] << 3; + int s23 = lfsr[1] >>> 7 | lfsr[2] << 9; + int s38 = lfsr[2] >>> 6 | lfsr[3] << 10; + int s51 = lfsr[3] >>> 3 | lfsr[4] << 13; + int s62 = lfsr[3] >>> 14 | lfsr[4] << 2; + + return (s0 ^ s13 ^ s23 ^ s38 ^ s51 ^ s62) & 0x0000FFFF; + } + + /** + * Get output from output function h(x). + * + * @return Output from h(x). + */ + private int getOutput() + { + int b1 = nfsr[0] >>> 1 | nfsr[1] << 15; + int b2 = nfsr[0] >>> 2 | nfsr[1] << 14; + int b4 = nfsr[0] >>> 4 | nfsr[1] << 12; + int b10 = nfsr[0] >>> 10 | nfsr[1] << 6; + int b31 = nfsr[1] >>> 15 | nfsr[2] << 1; + int b43 = nfsr[2] >>> 11 | nfsr[3] << 5; + int b56 = nfsr[3] >>> 8 | nfsr[4] << 8; + int b63 = nfsr[3] >>> 15 | nfsr[4] << 1; + int s3 = lfsr[0] >>> 3 | lfsr[1] << 13; + int s25 = lfsr[1] >>> 9 | lfsr[2] << 7; + int s46 = lfsr[2] >>> 14 | lfsr[3] << 2; + int s64 = lfsr[4]; + + return (s25 ^ b63 ^ s3 & s64 ^ s46 & s64 ^ s64 & b63 ^ s3 + & s25 & s46 ^ s3 & s46 & s64 ^ s3 & s46 & b63 ^ s25 & s46 & b63 ^ s46 + & s64 & b63 ^ b1 ^ b2 ^ b4 ^ b10 ^ b31 ^ b43 ^ b56) & 0x0000FFFF; + } + + /** + * Shift array 16 bits and add val to index.length - 1. + * + * @param array The array to shift. + * @param val The value to shift in. + * @return The shifted array with val added to index.length - 1. + */ + private int[] shift(int[] array, int val) + { + array[0] = array[1]; + array[1] = array[2]; + array[2] = array[3]; + array[3] = array[4]; + array[4] = val; + + return array; + } + + /** + * Set keys, reset cipher. + * + * @param keyBytes The key. + * @param ivBytes The IV. + */ + private void setKey(byte[] keyBytes, byte[] ivBytes) + { + ivBytes[8] = (byte)0xFF; + ivBytes[9] = (byte)0xFF; + workingKey = keyBytes; + workingIV = ivBytes; + + /** + * Load NFSR and LFSR + */ + int j = 0; + for (int i = 0; i < nfsr.length; i++) + { + nfsr[i] = (workingKey[j + 1] << 8 | workingKey[j] & 0xFF) & 0x0000FFFF; + lfsr[i] = (workingIV[j + 1] << 8 | workingIV[j] & 0xFF) & 0x0000FFFF; + j += 2; + } + } + + public void processBytes(byte[] in, int inOff, int len, byte[] out, + int outOff) + throws DataLengthException + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName() + + " not initialised"); + } + + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + for (int i = 0; i < len; i++) + { + out[outOff + i] = (byte)(in[inOff + i] ^ getKeyStream()); + } + } + + public void reset() + { + index = 2; + setKey(workingKey, workingIV); + initGrain(); + } + + /** + * Run Grain one round(i.e. 16 bits). + */ + private void oneRound() + { + output = getOutput(); + out[0] = (byte)output; + out[1] = (byte)(output >> 8); + + nfsr = shift(nfsr, getOutputNFSR() ^ lfsr[0]); + lfsr = shift(lfsr, getOutputLFSR()); + } + + public byte returnByte(byte in) + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName() + + " not initialised"); + } + return (byte)(in ^ getKeyStream()); + } + + private byte getKeyStream() + { + if (index > 1) + { + oneRound(); + index = 0; + } + return out[index++]; + } +} \ No newline at end of file diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/HC128Engine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/HC128Engine.java new file mode 100644 index 000000000..db662f002 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/HC128Engine.java @@ -0,0 +1,256 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.StreamCipher; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +/** + * HC-128 is a software-efficient stream cipher created by Hongjun Wu. It + * generates keystream from a 128-bit secret key and a 128-bit initialization + * vector. + *

+ * http://www.ecrypt.eu.org/stream/p3ciphers/hc/hc128_p3.pdf + *

+ * It is a third phase candidate in the eStream contest, and is patent-free. + * No attacks are known as of today (April 2007). See + * + * http://www.ecrypt.eu.org/stream/hcp3.html + *

+ */ +public class HC128Engine + implements StreamCipher +{ + private int[] p = new int[512]; + private int[] q = new int[512]; + private int cnt = 0; + + private static int f1(int x) + { + return rotateRight(x, 7) ^ rotateRight(x, 18) + ^ (x >>> 3); + } + + private static int f2(int x) + { + return rotateRight(x, 17) ^ rotateRight(x, 19) + ^ (x >>> 10); + } + + private int g1(int x, int y, int z) + { + return (rotateRight(x, 10) ^ rotateRight(z, 23)) + + rotateRight(y, 8); + } + + private int g2(int x, int y, int z) + { + return (rotateLeft(x, 10) ^ rotateLeft(z, 23)) + rotateLeft(y, 8); + } + + private static int rotateLeft( + int x, + int bits) + { + return (x << bits) | (x >>> -bits); + } + + private static int rotateRight( + int x, + int bits) + { + return (x >>> bits) | (x << -bits); + } + + private int h1(int x) + { + return q[x & 0xFF] + q[((x >> 16) & 0xFF) + 256]; + } + + private int h2(int x) + { + return p[x & 0xFF] + p[((x >> 16) & 0xFF) + 256]; + } + + private static int mod1024(int x) + { + return x & 0x3FF; + } + + private static int mod512(int x) + { + return x & 0x1FF; + } + + private static int dim(int x, int y) + { + return mod512(x - y); + } + + private int step() + { + int j = mod512(cnt); + int ret; + if (cnt < 512) + { + p[j] += g1(p[dim(j, 3)], p[dim(j, 10)], p[dim(j, 511)]); + ret = h1(p[dim(j, 12)]) ^ p[j]; + } + else + { + q[j] += g2(q[dim(j, 3)], q[dim(j, 10)], q[dim(j, 511)]); + ret = h2(q[dim(j, 12)]) ^ q[j]; + } + cnt = mod1024(cnt + 1); + return ret; + } + + private byte[] key, iv; + private boolean initialised; + + private void init() + { + if (key.length != 16) + { + throw new java.lang.IllegalArgumentException( + "The key must be 128 bits long"); + } + + cnt = 0; + + int[] w = new int[1280]; + + for (int i = 0; i < 16; i++) + { + w[i >> 2] |= (key[i] & 0xff) << (8 * (i & 0x3)); + } + System.arraycopy(w, 0, w, 4, 4); + + for (int i = 0; i < iv.length && i < 16; i++) + { + w[(i >> 2) + 8] |= (iv[i] & 0xff) << (8 * (i & 0x3)); + } + System.arraycopy(w, 8, w, 12, 4); + + for (int i = 16; i < 1280; i++) + { + w[i] = f2(w[i - 2]) + w[i - 7] + f1(w[i - 15]) + w[i - 16] + i; + } + + System.arraycopy(w, 256, p, 0, 512); + System.arraycopy(w, 768, q, 0, 512); + + for (int i = 0; i < 512; i++) + { + p[i] = step(); + } + for (int i = 0; i < 512; i++) + { + q[i] = step(); + } + + cnt = 0; + } + + public String getAlgorithmName() + { + return "HC-128"; + } + + /** + * Initialise a HC-128 cipher. + * + * @param forEncryption whether or not we are for encryption. Irrelevant, as + * encryption and decryption are the same. + * @param params the parameters required to set up the cipher. + * @throws IllegalArgumentException if the params argument is + * inappropriate (ie. the key is not 128 bit long). + */ + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + CipherParameters keyParam = params; + + if (params instanceof ParametersWithIV) + { + iv = ((ParametersWithIV)params).getIV(); + keyParam = ((ParametersWithIV)params).getParameters(); + } + else + { + iv = new byte[0]; + } + + if (keyParam instanceof KeyParameter) + { + key = ((KeyParameter)keyParam).getKey(); + init(); + } + else + { + throw new IllegalArgumentException( + "Invalid parameter passed to HC128 init - " + + params.getClass().getName()); + } + + initialised = true; + } + + private byte[] buf = new byte[4]; + private int idx = 0; + + private byte getByte() + { + if (idx == 0) + { + int step = step(); + buf[0] = (byte)(step & 0xFF); + step >>= 8; + buf[1] = (byte)(step & 0xFF); + step >>= 8; + buf[2] = (byte)(step & 0xFF); + step >>= 8; + buf[3] = (byte)(step & 0xFF); + } + byte ret = buf[idx]; + idx = idx + 1 & 0x3; + return ret; + } + + public void processBytes(byte[] in, int inOff, int len, byte[] out, + int outOff) throws DataLengthException + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName() + + " not initialised"); + } + + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + for (int i = 0; i < len; i++) + { + out[outOff + i] = (byte)(in[inOff + i] ^ getByte()); + } + } + + public void reset() + { + idx = 0; + init(); + } + + public byte returnByte(byte in) + { + return (byte)(in ^ getByte()); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/HC256Engine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/HC256Engine.java new file mode 100644 index 000000000..590569ef1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/HC256Engine.java @@ -0,0 +1,243 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.StreamCipher; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +/** + * HC-256 is a software-efficient stream cipher created by Hongjun Wu. It + * generates keystream from a 256-bit secret key and a 256-bit initialization + * vector. + *

+ * http://www.ecrypt.eu.org/stream/p3ciphers/hc/hc256_p3.pdf + *

+ * Its brother, HC-128, is a third phase candidate in the eStream contest. + * The algorithm is patent-free. No attacks are known as of today (April 2007). + * See + * + * http://www.ecrypt.eu.org/stream/hcp3.html + *

+ */ +public class HC256Engine + implements StreamCipher +{ + private int[] p = new int[1024]; + private int[] q = new int[1024]; + private int cnt = 0; + + private int step() + { + int j = cnt & 0x3FF; + int ret; + if (cnt < 1024) + { + int x = p[(j - 3 & 0x3FF)]; + int y = p[(j - 1023 & 0x3FF)]; + p[j] += p[(j - 10 & 0x3FF)] + + (rotateRight(x, 10) ^ rotateRight(y, 23)) + + q[((x ^ y) & 0x3FF)]; + + x = p[(j - 12 & 0x3FF)]; + ret = (q[x & 0xFF] + q[((x >> 8) & 0xFF) + 256] + + q[((x >> 16) & 0xFF) + 512] + q[((x >> 24) & 0xFF) + 768]) + ^ p[j]; + } + else + { + int x = q[(j - 3 & 0x3FF)]; + int y = q[(j - 1023 & 0x3FF)]; + q[j] += q[(j - 10 & 0x3FF)] + + (rotateRight(x, 10) ^ rotateRight(y, 23)) + + p[((x ^ y) & 0x3FF)]; + + x = q[(j - 12 & 0x3FF)]; + ret = (p[x & 0xFF] + p[((x >> 8) & 0xFF) + 256] + + p[((x >> 16) & 0xFF) + 512] + p[((x >> 24) & 0xFF) + 768]) + ^ q[j]; + } + cnt = cnt + 1 & 0x7FF; + return ret; + } + + private byte[] key, iv; + private boolean initialised; + + private void init() + { + if (key.length != 32 && key.length != 16) + { + throw new IllegalArgumentException( + "The key must be 128/256 bits long"); + } + + if (iv.length < 16) + { + throw new IllegalArgumentException( + "The IV must be at least 128 bits long"); + } + + if (key.length != 32) + { + byte[] k = new byte[32]; + + System.arraycopy(key, 0, k, 0, key.length); + System.arraycopy(key, 0, k, 16, key.length); + + key = k; + } + + if (iv.length < 32) + { + byte[] newIV = new byte[32]; + + System.arraycopy(iv, 0, newIV, 0, iv.length); + System.arraycopy(iv, 0, newIV, iv.length, newIV.length - iv.length); + + iv = newIV; + } + + cnt = 0; + + int[] w = new int[2560]; + + for (int i = 0; i < 32; i++) + { + w[i >> 2] |= (key[i] & 0xff) << (8 * (i & 0x3)); + } + + for (int i = 0; i < 32; i++) + { + w[(i >> 2) + 8] |= (iv[i] & 0xff) << (8 * (i & 0x3)); + } + + for (int i = 16; i < 2560; i++) + { + int x = w[i - 2]; + int y = w[i - 15]; + w[i] = (rotateRight(x, 17) ^ rotateRight(x, 19) ^ (x >>> 10)) + + w[i - 7] + + (rotateRight(y, 7) ^ rotateRight(y, 18) ^ (y >>> 3)) + + w[i - 16] + i; + } + + System.arraycopy(w, 512, p, 0, 1024); + System.arraycopy(w, 1536, q, 0, 1024); + + for (int i = 0; i < 4096; i++) + { + step(); + } + + cnt = 0; + } + + public String getAlgorithmName() + { + return "HC-256"; + } + + /** + * Initialise a HC-256 cipher. + * + * @param forEncryption whether or not we are for encryption. Irrelevant, as + * encryption and decryption are the same. + * @param params the parameters required to set up the cipher. + * @throws IllegalArgumentException if the params argument is + * inappropriate (ie. the key is not 256 bit long). + */ + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + CipherParameters keyParam = params; + + if (params instanceof ParametersWithIV) + { + iv = ((ParametersWithIV)params).getIV(); + keyParam = ((ParametersWithIV)params).getParameters(); + } + else + { + iv = new byte[0]; + } + + if (keyParam instanceof KeyParameter) + { + key = ((KeyParameter)keyParam).getKey(); + init(); + } + else + { + throw new IllegalArgumentException( + "Invalid parameter passed to HC256 init - " + + params.getClass().getName()); + } + + initialised = true; + } + + private byte[] buf = new byte[4]; + private int idx = 0; + + private byte getByte() + { + if (idx == 0) + { + int step = step(); + buf[0] = (byte)(step & 0xFF); + step >>= 8; + buf[1] = (byte)(step & 0xFF); + step >>= 8; + buf[2] = (byte)(step & 0xFF); + step >>= 8; + buf[3] = (byte)(step & 0xFF); + } + byte ret = buf[idx]; + idx = idx + 1 & 0x3; + return ret; + } + + public void processBytes(byte[] in, int inOff, int len, byte[] out, + int outOff) throws DataLengthException + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName() + + " not initialised"); + } + + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + for (int i = 0; i < len; i++) + { + out[outOff + i] = (byte)(in[inOff + i] ^ getByte()); + } + } + + public void reset() + { + idx = 0; + init(); + } + + public byte returnByte(byte in) + { + return (byte)(in ^ getByte()); + } + + private static int rotateRight( + int x, + int bits) + { + return (x >>> bits) | (x << -bits); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/IDEAEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/IDEAEngine.java new file mode 100644 index 000000000..8e4f22a7e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/IDEAEngine.java @@ -0,0 +1,366 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * A class that provides a basic International Data Encryption Algorithm (IDEA) engine. + *

+ * This implementation is based on the "HOWTO: INTERNATIONAL DATA ENCRYPTION ALGORITHM" + * implementation summary by Fauzan Mirza (F.U.Mirza@sheffield.ac.uk). (baring 1 typo at the + * end of the mulinv function!). + *

+ * It can be found at ftp://ftp.funet.fi/pub/crypt/cryptography/symmetric/idea/ + *

+ * Note 1: This algorithm is patented in the USA, Japan, and Europe including + * at least Austria, France, Germany, Italy, Netherlands, Spain, Sweden, Switzerland + * and the United Kingdom. Non-commercial use is free, however any commercial + * products are liable for royalties. Please see + * www.mediacrypt.com for + * further details. This announcement has been included at the request of + * the patent holders. + *

+ * Note 2: Due to the requests concerning the above, this algorithm is now only + * included in the extended Bouncy Castle provider and JCE signed jars. It is + * not included in the default distributions. + */ +public class IDEAEngine + implements BlockCipher +{ + protected static final int BLOCK_SIZE = 8; + + private int[] workingKey = null; + + /** + * standard constructor. + */ + public IDEAEngine() + { + } + + /** + * initialise an IDEA cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + workingKey = generateWorkingKey(forEncryption, + ((KeyParameter)params).getKey()); + return; + } + + throw new IllegalArgumentException("invalid parameter passed to IDEA init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "IDEA"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("IDEA engine not initialised"); + } + + if ((inOff + BLOCK_SIZE) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + BLOCK_SIZE) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + ideaFunc(workingKey, in, inOff, out, outOff); + + return BLOCK_SIZE; + } + + public void reset() + { + } + + private static final int MASK = 0xffff; + private static final int BASE = 0x10001; + + private int bytesToWord( + byte[] in, + int inOff) + { + return ((in[inOff] << 8) & 0xff00) + (in[inOff + 1] & 0xff); + } + + private void wordToBytes( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)(word >>> 8); + out[outOff + 1] = (byte)word; + } + + /** + * return x = x * y where the multiplication is done modulo + * 65537 (0x10001) (as defined in the IDEA specification) and + * a zero input is taken to be 65536 (0x10000). + * + * @param x the x value + * @param y the y value + * @return x = x * y + */ + private int mul( + int x, + int y) + { + if (x == 0) + { + x = (BASE - y); + } + else if (y == 0) + { + x = (BASE - x); + } + else + { + int p = x * y; + + y = p & MASK; + x = p >>> 16; + x = y - x + ((y < x) ? 1 : 0); + } + + return x & MASK; + } + + private void ideaFunc( + int[] workingKey, + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int x0, x1, x2, x3, t0, t1; + int keyOff = 0; + + x0 = bytesToWord(in, inOff); + x1 = bytesToWord(in, inOff + 2); + x2 = bytesToWord(in, inOff + 4); + x3 = bytesToWord(in, inOff + 6); + + for (int round = 0; round < 8; round++) + { + x0 = mul(x0, workingKey[keyOff++]); + x1 += workingKey[keyOff++]; + x1 &= MASK; + x2 += workingKey[keyOff++]; + x2 &= MASK; + x3 = mul(x3, workingKey[keyOff++]); + + t0 = x1; + t1 = x2; + x2 ^= x0; + x1 ^= x3; + + x2 = mul(x2, workingKey[keyOff++]); + x1 += x2; + x1 &= MASK; + + x1 = mul(x1, workingKey[keyOff++]); + x2 += x1; + x2 &= MASK; + + x0 ^= x1; + x3 ^= x2; + x1 ^= t1; + x2 ^= t0; + } + + wordToBytes(mul(x0, workingKey[keyOff++]), out, outOff); + wordToBytes(x2 + workingKey[keyOff++], out, outOff + 2); /* NB: Order */ + wordToBytes(x1 + workingKey[keyOff++], out, outOff + 4); + wordToBytes(mul(x3, workingKey[keyOff]), out, outOff + 6); + } + + /** + * The following function is used to expand the user key to the encryption + * subkey. The first 16 bytes are the user key, and the rest of the subkey + * is calculated by rotating the previous 16 bytes by 25 bits to the left, + * and so on until the subkey is completed. + */ + private int[] expandKey( + byte[] uKey) + { + int[] key = new int[52]; + + if (uKey.length < 16) + { + byte[] tmp = new byte[16]; + + System.arraycopy(uKey, 0, tmp, tmp.length - uKey.length, uKey.length); + + uKey = tmp; + } + + for (int i = 0; i < 8; i++) + { + key[i] = bytesToWord(uKey, i * 2); + } + + for (int i = 8; i < 52; i++) + { + if ((i & 7) < 6) + { + key[i] = ((key[i - 7] & 127) << 9 | key[i - 6] >> 7) & MASK; + } + else if ((i & 7) == 6) + { + key[i] = ((key[i - 7] & 127) << 9 | key[i - 14] >> 7) & MASK; + } + else + { + key[i] = ((key[i - 15] & 127) << 9 | key[i - 14] >> 7) & MASK; + } + } + + return key; + } + + /** + * This function computes multiplicative inverse using Euclid's Greatest + * Common Divisor algorithm. Zero and one are self inverse. + *

+ * i.e. x * mulInv(x) == 1 (modulo BASE) + */ + private int mulInv( + int x) + { + int t0, t1, q, y; + + if (x < 2) + { + return x; + } + + t0 = 1; + t1 = BASE / x; + y = BASE % x; + + while (y != 1) + { + q = x / y; + x = x % y; + t0 = (t0 + (t1 * q)) & MASK; + if (x == 1) + { + return t0; + } + q = y / x; + y = y % x; + t1 = (t1 + (t0 * q)) & MASK; + } + + return (1 - t1) & MASK; + } + + /** + * Return the additive inverse of x. + *

+ * i.e. x + addInv(x) == 0 + */ + int addInv( + int x) + { + return (0 - x) & MASK; + } + + /** + * The function to invert the encryption subkey to the decryption subkey. + * It also involves the multiplicative inverse and the additive inverse functions. + */ + private int[] invertKey( + int[] inKey) + { + int t1, t2, t3, t4; + int p = 52; /* We work backwards */ + int[] key = new int[52]; + int inOff = 0; + + t1 = mulInv(inKey[inOff++]); + t2 = addInv(inKey[inOff++]); + t3 = addInv(inKey[inOff++]); + t4 = mulInv(inKey[inOff++]); + key[--p] = t4; + key[--p] = t3; + key[--p] = t2; + key[--p] = t1; + + for (int round = 1; round < 8; round++) + { + t1 = inKey[inOff++]; + t2 = inKey[inOff++]; + key[--p] = t2; + key[--p] = t1; + + t1 = mulInv(inKey[inOff++]); + t2 = addInv(inKey[inOff++]); + t3 = addInv(inKey[inOff++]); + t4 = mulInv(inKey[inOff++]); + key[--p] = t4; + key[--p] = t2; /* NB: Order */ + key[--p] = t3; + key[--p] = t1; + } + + t1 = inKey[inOff++]; + t2 = inKey[inOff++]; + key[--p] = t2; + key[--p] = t1; + + t1 = mulInv(inKey[inOff++]); + t2 = addInv(inKey[inOff++]); + t3 = addInv(inKey[inOff++]); + t4 = mulInv(inKey[inOff]); + key[--p] = t4; + key[--p] = t3; + key[--p] = t2; + key[--p] = t1; + + return key; + } + + private int[] generateWorkingKey( + boolean forEncryption, + byte[] userKey) + { + if (forEncryption) + { + return expandKey(userKey); + } + else + { + return invertKey(expandKey(userKey)); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/IESEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/IESEngine.java new file mode 100644 index 000000000..9867cc641 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/IESEngine.java @@ -0,0 +1,257 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BasicAgreement; +import com.google.bitcoin.bouncycastle.crypto.BufferedBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DerivationFunction; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; +import com.google.bitcoin.bouncycastle.crypto.Mac; +import com.google.bitcoin.bouncycastle.crypto.params.IESParameters; +import com.google.bitcoin.bouncycastle.crypto.params.IESWithCipherParameters; +import com.google.bitcoin.bouncycastle.crypto.params.KDFParameters; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +import java.math.BigInteger; + +/** + * support class for constructing intergrated encryption ciphers + * for doing basic message exchanges on top of key agreement ciphers + */ +public class IESEngine +{ + BasicAgreement agree; + DerivationFunction kdf; + Mac mac; + BufferedBlockCipher cipher; + byte[] macBuf; + + boolean forEncryption; + CipherParameters privParam, pubParam; + IESParameters param; + + /** + * set up for use with stream mode, where the key derivation function + * is used to provide a stream of bytes to xor with the message. + * + * @param agree the key agreement used as the basis for the encryption + * @param kdf the key derivation function used for byte generation + * @param mac the message authentication code generator for the message + */ + public IESEngine( + BasicAgreement agree, + DerivationFunction kdf, + Mac mac) + { + this.agree = agree; + this.kdf = kdf; + this.mac = mac; + this.macBuf = new byte[mac.getMacSize()]; + this.cipher = null; + } + + /** + * set up for use in conjunction with a block cipher to handle the + * message. + * + * @param agree the key agreement used as the basis for the encryption + * @param kdf the key derivation function used for byte generation + * @param mac the message authentication code generator for the message + * @param cipher the cipher to used for encrypting the message + */ + public IESEngine( + BasicAgreement agree, + DerivationFunction kdf, + Mac mac, + BufferedBlockCipher cipher) + { + this.agree = agree; + this.kdf = kdf; + this.mac = mac; + this.macBuf = new byte[mac.getMacSize()]; + this.cipher = cipher; + } + + /** + * Initialise the encryptor. + * + * @param forEncryption whether or not this is encryption/decryption. + * @param privParam our private key parameters + * @param pubParam the recipient's/sender's public key parameters + * @param param encoding and derivation parameters. + */ + public void init( + boolean forEncryption, + CipherParameters privParam, + CipherParameters pubParam, + CipherParameters param) + { + this.forEncryption = forEncryption; + this.privParam = privParam; + this.pubParam = pubParam; + this.param = (IESParameters)param; + } + + private byte[] decryptBlock( + byte[] in_enc, + int inOff, + int inLen, + byte[] z) + throws InvalidCipherTextException + { + byte[] M = null; + KeyParameter macKey = null; + KDFParameters kParam = new KDFParameters(z, param.getDerivationV()); + int macKeySize = param.getMacKeySize(); + + kdf.init(kParam); + + inLen -= mac.getMacSize(); + + if (cipher == null) // stream mode + { + byte[] buf = generateKdfBytes(kParam, inLen + (macKeySize / 8)); + + M = new byte[inLen]; + + for (int i = 0; i != inLen; i++) + { + M[i] = (byte)(in_enc[inOff + i] ^ buf[i]); + } + + macKey = new KeyParameter(buf, inLen, (macKeySize / 8)); + } + else + { + int cipherKeySize = ((IESWithCipherParameters)param).getCipherKeySize(); + byte[] buf = generateKdfBytes(kParam, (cipherKeySize / 8) + (macKeySize / 8)); + + cipher.init(false, new KeyParameter(buf, 0, (cipherKeySize / 8))); + + byte[] tmp = new byte[cipher.getOutputSize(inLen)]; + + int len = cipher.processBytes(in_enc, inOff, inLen, tmp, 0); + + len += cipher.doFinal(tmp, len); + + M = new byte[len]; + + System.arraycopy(tmp, 0, M, 0, len); + + macKey = new KeyParameter(buf, (cipherKeySize / 8), (macKeySize / 8)); + } + + byte[] macIV = param.getEncodingV(); + + mac.init(macKey); + mac.update(in_enc, inOff, inLen); + mac.update(macIV, 0, macIV.length); + mac.doFinal(macBuf, 0); + + inOff += inLen; + + for (int t = 0; t < macBuf.length; t++) + { + if (macBuf[t] != in_enc[inOff + t]) + { + throw (new InvalidCipherTextException("Mac codes failed to equal.")); + } + } + + return M; + } + + private byte[] encryptBlock( + byte[] in, + int inOff, + int inLen, + byte[] z) + throws InvalidCipherTextException + { + byte[] C = null; + KeyParameter macKey = null; + KDFParameters kParam = new KDFParameters(z, param.getDerivationV()); + int c_text_length = 0; + int macKeySize = param.getMacKeySize(); + + if (cipher == null) // stream mode + { + byte[] buf = generateKdfBytes(kParam, inLen + (macKeySize / 8)); + + C = new byte[inLen + mac.getMacSize()]; + c_text_length = inLen; + + for (int i = 0; i != inLen; i++) + { + C[i] = (byte)(in[inOff + i] ^ buf[i]); + } + + macKey = new KeyParameter(buf, inLen, (macKeySize / 8)); + } + else + { + int cipherKeySize = ((IESWithCipherParameters)param).getCipherKeySize(); + byte[] buf = generateKdfBytes(kParam, (cipherKeySize / 8) + (macKeySize / 8)); + + cipher.init(true, new KeyParameter(buf, 0, (cipherKeySize / 8))); + + c_text_length = cipher.getOutputSize(inLen); + + byte[] tmp = new byte[c_text_length]; + + int len = cipher.processBytes(in, inOff, inLen, tmp, 0); + + len += cipher.doFinal(tmp, len); + + C = new byte[len + mac.getMacSize()]; + c_text_length = len; + + System.arraycopy(tmp, 0, C, 0, len); + + macKey = new KeyParameter(buf, (cipherKeySize / 8), (macKeySize / 8)); + } + + byte[] macIV = param.getEncodingV(); + + mac.init(macKey); + mac.update(C, 0, c_text_length); + mac.update(macIV, 0, macIV.length); + // + // return the message and it's MAC + // + mac.doFinal(C, c_text_length); + return C; + } + + private byte[] generateKdfBytes( + KDFParameters kParam, + int length) + { + byte[] buf = new byte[length]; + + kdf.init(kParam); + + kdf.generateBytes(buf, 0, buf.length); + + return buf; + } + + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + agree.init(privParam); + + BigInteger z = agree.calculateAgreement(pubParam); + + if (forEncryption) + { + return encryptBlock(in, inOff, inLen, z.toByteArray()); + } + else + { + return decryptBlock(in, inOff, inLen, z.toByteArray()); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/ISAACEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/ISAACEngine.java new file mode 100644 index 000000000..242fb152e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/ISAACEngine.java @@ -0,0 +1,245 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.StreamCipher; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * Implementation of Bob Jenkin's ISAAC (Indirection Shift Accumulate Add and Count). + * see: http://www.burtleburtle.net/bob/rand/isaacafa.html +*/ +public class ISAACEngine + implements StreamCipher +{ + // Constants + private final int sizeL = 8, + stateArraySize = sizeL<<5; // 256 + + // Cipher's internal state + private int[] engineState = null, // mm + results = null; // randrsl + private int a = 0, b = 0, c = 0; + + // Engine state + private int index = 0; + private byte[] keyStream = new byte[stateArraySize<<2], // results expanded into bytes + workingKey = null; + private boolean initialised = false; + + /** + * initialise an ISAAC cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("invalid parameter passed to ISAAC init - " + params.getClass().getName()); + } + /* + * ISAAC encryption and decryption is completely + * symmetrical, so the 'forEncryption' is + * irrelevant. + */ + KeyParameter p = (KeyParameter)params; + setKey(p.getKey()); + + return; + } + + public byte returnByte(byte in) + { + if (index == 0) + { + isaac(); + keyStream = intToByteLittle(results); + } + byte out = (byte)(keyStream[index]^in); + index = (index + 1) & 1023; + + return out; + } + + public void processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName()+" not initialised"); + } + + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + for (int i = 0; i < len; i++) + { + if (index == 0) + { + isaac(); + keyStream = intToByteLittle(results); + } + out[i+outOff] = (byte)(keyStream[index]^in[i+inOff]); + index = (index + 1) & 1023; + } + } + + public String getAlgorithmName() + { + return "ISAAC"; + } + + public void reset() + { + setKey(workingKey); + } + + // Private implementation + private void setKey(byte[] keyBytes) + { + workingKey = keyBytes; + + if (engineState == null) + { + engineState = new int[stateArraySize]; + } + + if (results == null) + { + results = new int[stateArraySize]; + } + + int i, j, k; + + // Reset state + for (i = 0; i < stateArraySize; i++) + { + engineState[i] = results[i] = 0; + } + a = b = c = 0; + + // Reset index counter for output + index = 0; + + // Convert the key bytes to ints and put them into results[] for initialization + byte[] t = new byte[keyBytes.length + (keyBytes.length & 3)]; + System.arraycopy(keyBytes, 0, t, 0, keyBytes.length); + for (i = 0; i < t.length; i+=4) + { + results[i>>2] = byteToIntLittle(t, i); + } + + // It has begun? + int[] abcdefgh = new int[sizeL]; + + for (i = 0; i < sizeL; i++) + { + abcdefgh[i] = 0x9e3779b9; // Phi (golden ratio) + } + + for (i = 0; i < 4; i++) + { + mix(abcdefgh); + } + + for (i = 0; i < 2; i++) + { + for (j = 0; j < stateArraySize; j+=sizeL) + { + for (k = 0; k < sizeL; k++) + { + abcdefgh[k] += (i<1) ? results[j+k] : engineState[j+k]; + } + + mix(abcdefgh); + + for (k = 0; k < sizeL; k++) + { + engineState[j+k] = abcdefgh[k]; + } + } + } + + isaac(); + + initialised = true; + } + + private void isaac() + { + int i, x, y; + + b += ++c; + for (i = 0; i < stateArraySize; i++) + { + x = engineState[i]; + switch (i & 3) + { + case 0: a ^= (a << 13); break; + case 1: a ^= (a >>> 6); break; + case 2: a ^= (a << 2); break; + case 3: a ^= (a >>> 16); break; + } + a += engineState[(i+128) & 0xFF]; + engineState[i] = y = engineState[(x >>> 2) & 0xFF] + a + b; + results[i] = b = engineState[(y >>> 10) & 0xFF] + x; + } + } + + private void mix(int[] x) + { + x[0]^=x[1]<< 11; x[3]+=x[0]; x[1]+=x[2]; + x[1]^=x[2]>>> 2; x[4]+=x[1]; x[2]+=x[3]; + x[2]^=x[3]<< 8; x[5]+=x[2]; x[3]+=x[4]; + x[3]^=x[4]>>>16; x[6]+=x[3]; x[4]+=x[5]; + x[4]^=x[5]<< 10; x[7]+=x[4]; x[5]+=x[6]; + x[5]^=x[6]>>> 4; x[0]+=x[5]; x[6]+=x[7]; + x[6]^=x[7]<< 8; x[1]+=x[6]; x[7]+=x[0]; + x[7]^=x[0]>>> 9; x[2]+=x[7]; x[0]+=x[1]; + } + + private int byteToIntLittle(byte[] x, int offset) + { + return (int)(x[offset++] & 0xFF) | + ((x[offset++] & 0xFF) << 8) | + ((x[offset++] & 0xFF) << 16) | + (x[offset++] << 24); + } + + private byte[] intToByteLittle(int x) + { + byte[] out = new byte[4]; + out[3] = (byte)x; + out[2] = (byte)(x >>> 8); + out[1] = (byte)(x >>> 16); + out[0] = (byte)(x >>> 24); + return out; + } + + private byte[] intToByteLittle(int[] x) + { + byte[] out = new byte[4*x.length]; + for (int i = 0, j = 0; i < x.length; i++,j+=4) + { + System.arraycopy(intToByteLittle(x[i]), 0, out, j, 4); + } + return out; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/NaccacheSternEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/NaccacheSternEngine.java new file mode 100644 index 000000000..d4cbf2bca --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/NaccacheSternEngine.java @@ -0,0 +1,438 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import java.math.BigInteger; +import java.util.Vector; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; +import com.google.bitcoin.bouncycastle.crypto.params.NaccacheSternKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.NaccacheSternPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.util.Arrays; + + +/** + * NaccacheStern Engine. For details on this cipher, please see + * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + */ +public class NaccacheSternEngine + implements AsymmetricBlockCipher +{ + private boolean forEncryption; + + private NaccacheSternKeyParameters key; + + private Vector[] lookup = null; + + private boolean debug = false; + + private static BigInteger ZERO = BigInteger.valueOf(0); + private static BigInteger ONE = BigInteger.valueOf(1); + + /** + * Initializes this algorithm. Must be called before all other Functions. + * + * @see com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher#init(boolean, + * com.google.bitcoin.bouncycastle.crypto.CipherParameters) + */ + public void init(boolean forEncryption, CipherParameters param) + { + this.forEncryption = forEncryption; + + if (param instanceof ParametersWithRandom) + { + param = ((ParametersWithRandom) param).getParameters(); + } + + key = (NaccacheSternKeyParameters)param; + + // construct lookup table for faster decryption if necessary + if (!this.forEncryption) + { + if (debug) + { + System.out.println("Constructing lookup Array"); + } + NaccacheSternPrivateKeyParameters priv = (NaccacheSternPrivateKeyParameters)key; + Vector primes = priv.getSmallPrimes(); + lookup = new Vector[primes.size()]; + for (int i = 0; i < primes.size(); i++) + { + BigInteger actualPrime = (BigInteger)primes.elementAt(i); + int actualPrimeValue = actualPrime.intValue(); + + lookup[i] = new Vector(); + lookup[i].addElement(ONE); + + if (debug) + { + System.out.println("Constructing lookup ArrayList for " + actualPrimeValue); + } + + BigInteger accJ = ZERO; + + for (int j = 1; j < actualPrimeValue; j++) + { + accJ = accJ.add(priv.getPhi_n()); + BigInteger comp = accJ.divide(actualPrime); + lookup[i].addElement(priv.getG().modPow(comp, priv.getModulus())); + } + } + } + } + + public void setDebug(boolean debug) + { + this.debug = debug; + } + + /** + * Returns the input block size of this algorithm. + * + * @see com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher#getInputBlockSize() + */ + public int getInputBlockSize() + { + if (forEncryption) + { + // We can only encrypt values up to lowerSigmaBound + return (key.getLowerSigmaBound() + 7) / 8 - 1; + } + else + { + // We pad to modulus-size bytes for easier decryption. + return key.getModulus().toByteArray().length; + } + } + + /** + * Returns the output block size of this algorithm. + * + * @see com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher#getOutputBlockSize() + */ + public int getOutputBlockSize() + { + if (forEncryption) + { + // encrypted Data is always padded up to modulus size + return key.getModulus().toByteArray().length; + } + else + { + // decrypted Data has upper limit lowerSigmaBound + return (key.getLowerSigmaBound() + 7) / 8 - 1; + } + } + + /** + * Process a single Block using the Naccache-Stern algorithm. + * + * @see com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher#processBlock(byte[], + * int, int) + */ + public byte[] processBlock(byte[] in, int inOff, int len) throws InvalidCipherTextException + { + if (key == null) + { + throw new IllegalStateException("NaccacheStern engine not initialised"); + } + if (len > (getInputBlockSize() + 1)) + { + throw new DataLengthException("input too large for Naccache-Stern cipher.\n"); + } + + if (!forEncryption) + { + // At decryption make sure that we receive padded data blocks + if (len < getInputBlockSize()) + { + throw new InvalidCipherTextException("BlockLength does not match modulus for Naccache-Stern cipher.\n"); + } + } + + byte[] block; + + if (inOff != 0 || len != in.length) + { + block = new byte[len]; + System.arraycopy(in, inOff, block, 0, len); + } + else + { + block = in; + } + + // transform input into BigInteger + BigInteger input = new BigInteger(1, block); + if (debug) + { + System.out.println("input as BigInteger: " + input); + } + byte[] output; + if (forEncryption) + { + output = encrypt(input); + } + else + { + Vector plain = new Vector(); + NaccacheSternPrivateKeyParameters priv = (NaccacheSternPrivateKeyParameters)key; + Vector primes = priv.getSmallPrimes(); + // Get Chinese Remainders of CipherText + for (int i = 0; i < primes.size(); i++) + { + BigInteger exp = input.modPow(priv.getPhi_n().divide((BigInteger)primes.elementAt(i)), priv.getModulus()); + Vector al = lookup[i]; + if (lookup[i].size() != ((BigInteger)primes.elementAt(i)).intValue()) + { + if (debug) + { + System.out.println("Prime is " + primes.elementAt(i) + ", lookup table has size " + al.size()); + } + throw new InvalidCipherTextException("Error in lookup Array for " + + ((BigInteger)primes.elementAt(i)).intValue() + + ": Size mismatch. Expected ArrayList with length " + + ((BigInteger)primes.elementAt(i)).intValue() + " but found ArrayList of length " + + lookup[i].size()); + } + int lookedup = al.indexOf(exp); + + if (lookedup == -1) + { + if (debug) + { + System.out.println("Actual prime is " + primes.elementAt(i)); + System.out.println("Decrypted value is " + exp); + + System.out.println("LookupList for " + primes.elementAt(i) + " with size " + lookup[i].size() + + " is: "); + for (int j = 0; j < lookup[i].size(); j++) + { + System.out.println(lookup[i].elementAt(j)); + } + } + throw new InvalidCipherTextException("Lookup failed"); + } + plain.addElement(BigInteger.valueOf(lookedup)); + } + BigInteger test = chineseRemainder(plain, primes); + + // Should not be used as an oracle, so reencrypt output to see + // if it corresponds to input + + // this breaks probabilisic encryption, so disable it. Anyway, we do + // use the first n primes for key generation, so it is pretty easy + // to guess them. But as stated in the paper, this is not a security + // breach. So we can just work with the correct sigma. + + // if (debug) { + // System.out.println("Decryption is " + test); + // } + // if ((key.getG().modPow(test, key.getModulus())).equals(input)) { + // output = test.toByteArray(); + // } else { + // if(debug){ + // System.out.println("Engine seems to be used as an oracle, + // returning null"); + // } + // output = null; + // } + + output = test.toByteArray(); + + } + + return output; + } + + /** + * Encrypts a BigInteger aka Plaintext with the public key. + * + * @param plain + * The BigInteger to encrypt + * @return The byte[] representation of the encrypted BigInteger (i.e. + * crypted.toByteArray()) + */ + public byte[] encrypt(BigInteger plain) + { + // Always return modulus size values 0-padded at the beginning + // 0-padding at the beginning is correctly parsed by BigInteger :) + byte[] output = key.getModulus().toByteArray(); + Arrays.fill(output, (byte)0); + byte[] tmp = key.getG().modPow(plain, key.getModulus()).toByteArray(); + System + .arraycopy(tmp, 0, output, output.length - tmp.length, + tmp.length); + if (debug) + { + System.out + .println("Encrypted value is: " + new BigInteger(output)); + } + return output; + } + + /** + * Adds the contents of two encrypted blocks mod sigma + * + * @param block1 + * the first encrypted block + * @param block2 + * the second encrypted block + * @return encrypt((block1 + block2) mod sigma) + * @throws InvalidCipherTextException + */ + public byte[] addCryptedBlocks(byte[] block1, byte[] block2) + throws InvalidCipherTextException + { + // check for correct blocksize + if (forEncryption) + { + if ((block1.length > getOutputBlockSize()) + || (block2.length > getOutputBlockSize())) + { + throw new InvalidCipherTextException( + "BlockLength too large for simple addition.\n"); + } + } + else + { + if ((block1.length > getInputBlockSize()) + || (block2.length > getInputBlockSize())) + { + throw new InvalidCipherTextException( + "BlockLength too large for simple addition.\n"); + } + } + + // calculate resulting block + BigInteger m1Crypt = new BigInteger(1, block1); + BigInteger m2Crypt = new BigInteger(1, block2); + BigInteger m1m2Crypt = m1Crypt.multiply(m2Crypt); + m1m2Crypt = m1m2Crypt.mod(key.getModulus()); + if (debug) + { + System.out.println("c(m1) as BigInteger:....... " + m1Crypt); + System.out.println("c(m2) as BigInteger:....... " + m2Crypt); + System.out.println("c(m1)*c(m2)%n = c(m1+m2)%n: " + m1m2Crypt); + } + + byte[] output = key.getModulus().toByteArray(); + Arrays.fill(output, (byte)0); + System.arraycopy(m1m2Crypt.toByteArray(), 0, output, output.length + - m1m2Crypt.toByteArray().length, + m1m2Crypt.toByteArray().length); + + return output; + } + + /** + * Convenience Method for data exchange with the cipher. + * + * Determines blocksize and splits data to blocksize. + * + * @param data the data to be processed + * @return the data after it went through the NaccacheSternEngine. + * @throws InvalidCipherTextException + */ + public byte[] processData(byte[] data) throws InvalidCipherTextException + { + if (debug) + { + System.out.println(); + } + if (data.length > getInputBlockSize()) + { + int inBlocksize = getInputBlockSize(); + int outBlocksize = getOutputBlockSize(); + if (debug) + { + System.out.println("Input blocksize is: " + inBlocksize + " bytes"); + System.out.println("Output blocksize is: " + outBlocksize + " bytes"); + System.out.println("Data has length:.... " + data.length + " bytes"); + } + int datapos = 0; + int retpos = 0; + byte[] retval = new byte[(data.length / inBlocksize + 1) * outBlocksize]; + while (datapos < data.length) + { + byte[] tmp; + if (datapos + inBlocksize < data.length) + { + tmp = processBlock(data, datapos, inBlocksize); + datapos += inBlocksize; + } + else + { + tmp = processBlock(data, datapos, data.length - datapos); + datapos += data.length - datapos; + } + if (debug) + { + System.out.println("new datapos is " + datapos); + } + if (tmp != null) + { + System.arraycopy(tmp, 0, retval, retpos, tmp.length); + + retpos += tmp.length; + } + else + { + if (debug) + { + System.out.println("cipher returned null"); + } + throw new InvalidCipherTextException("cipher returned null"); + } + } + byte[] ret = new byte[retpos]; + System.arraycopy(retval, 0, ret, 0, retpos); + if (debug) + { + System.out.println("returning " + ret.length + " bytes"); + } + return ret; + } + else + { + if (debug) + { + System.out.println("data size is less then input block size, processing directly"); + } + return processBlock(data, 0, data.length); + } + } + + /** + * Computes the integer x that is expressed through the given primes and the + * congruences with the chinese remainder theorem (CRT). + * + * @param congruences + * the congruences c_i + * @param primes + * the primes p_i + * @return an integer x for that x % p_i == c_i + */ + private static BigInteger chineseRemainder(Vector congruences, Vector primes) + { + BigInteger retval = ZERO; + BigInteger all = ONE; + for (int i = 0; i < primes.size(); i++) + { + all = all.multiply((BigInteger)primes.elementAt(i)); + } + for (int i = 0; i < primes.size(); i++) + { + BigInteger a = (BigInteger)primes.elementAt(i); + BigInteger b = all.divide(a); + BigInteger b_ = b.modInverse(a); + BigInteger tmp = b.multiply(b_); + tmp = tmp.multiply((BigInteger)congruences.elementAt(i)); + retval = retval.add(tmp); + } + + return retval.mod(all); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/NoekeonEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/NoekeonEngine.java new file mode 100644 index 000000000..345e663ec --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/NoekeonEngine.java @@ -0,0 +1,262 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * A Noekeon engine, using direct-key mode. + */ + +public class NoekeonEngine + implements BlockCipher +{ + private static final int genericSize = 16; // Block and key size, as well as the amount of rounds. + + private static final int[] nullVector = + { + 0x00, 0x00, 0x00, 0x00 // Used in decryption + }, + + roundConstants = + { + 0x80, 0x1b, 0x36, 0x6c, + 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, + 0xc6, 0x97, 0x35, 0x6a, + 0xd4 + }; + + private int[] state = new int[4], // a + subKeys = new int[4], // k + decryptKeys = new int[4]; + + private boolean _initialised, + _forEncryption; + + /** + * Create an instance of the Noekeon encryption algorithm + * and set some defaults + */ + public NoekeonEngine() + { + _initialised = false; + } + + public String getAlgorithmName() + { + return "Noekeon"; + } + + public int getBlockSize() + { + return genericSize; + } + + /** + * initialise + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("invalid parameter passed to Noekeon init - " + params.getClass().getName()); + } + + _forEncryption = forEncryption; + _initialised = true; + + KeyParameter p = (KeyParameter)params; + + setKey(p.getKey()); + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (!_initialised) + { + throw new IllegalStateException(getAlgorithmName()+" not initialised"); + } + + if ((inOff + genericSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + genericSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + return (_forEncryption) ? encryptBlock(in, inOff, out, outOff) + : decryptBlock(in, inOff, out, outOff); + } + + public void reset() + { + } + + /** + * Re-key the cipher. + *

+ * @param key the key to be used + */ + private void setKey( + byte[] key) + { + subKeys[0] = bytesToIntBig(key, 0); + subKeys[1] = bytesToIntBig(key, 4); + subKeys[2] = bytesToIntBig(key, 8); + subKeys[3] = bytesToIntBig(key, 12); + } + + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + state[0] = bytesToIntBig(in, inOff); + state[1] = bytesToIntBig(in, inOff+4); + state[2] = bytesToIntBig(in, inOff+8); + state[3] = bytesToIntBig(in, inOff+12); + + int i; + for (i = 0; i < genericSize; i++) + { + state[0] ^= roundConstants[i]; + theta(state, subKeys); + pi1(state); + gamma(state); + pi2(state); + } + + state[0] ^= roundConstants[i]; + theta(state, subKeys); + + intToBytesBig(state[0], out, outOff); + intToBytesBig(state[1], out, outOff+4); + intToBytesBig(state[2], out, outOff+8); + intToBytesBig(state[3], out, outOff+12); + + return genericSize; + } + + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + state[0] = bytesToIntBig(in, inOff); + state[1] = bytesToIntBig(in, inOff+4); + state[2] = bytesToIntBig(in, inOff+8); + state[3] = bytesToIntBig(in, inOff+12); + + System.arraycopy(subKeys, 0, decryptKeys, 0, subKeys.length); + theta(decryptKeys, nullVector); + + int i; + for (i = genericSize; i > 0; i--) + { + theta(state, decryptKeys); + state[0] ^= roundConstants[i]; + pi1(state); + gamma(state); + pi2(state); + } + + theta(state, decryptKeys); + state[0] ^= roundConstants[i]; + + intToBytesBig(state[0], out, outOff); + intToBytesBig(state[1], out, outOff+4); + intToBytesBig(state[2], out, outOff+8); + intToBytesBig(state[3], out, outOff+12); + + return genericSize; + } + + private void gamma(int[] a) + { + a[1] ^= ~a[3] & ~a[2]; + a[0] ^= a[2] & a[1]; + + int tmp = a[3]; + a[3] = a[0]; + a[0] = tmp; + a[2] ^= a[0]^a[1]^a[3]; + + a[1] ^= ~a[3] & ~a[2]; + a[0] ^= a[2] & a[1]; + } + + private void theta(int[] a, int[] k) + { + int tmp; + + tmp = a[0]^a[2]; + tmp ^= rotl(tmp,8)^rotl(tmp,24); + a[1] ^= tmp; + a[3] ^= tmp; + + for (int i = 0; i < 4; i++) + { + a[i] ^= k[i]; + } + + tmp = a[1]^a[3]; + tmp ^= rotl(tmp,8)^rotl(tmp,24); + a[0] ^= tmp; + a[2] ^= tmp; + } + + private void pi1(int[] a) + { + a[1] = rotl(a[1], 1); + a[2] = rotl(a[2], 5); + a[3] = rotl(a[3], 2); + } + + private void pi2(int[] a) + { + a[1] = rotl(a[1], 31); + a[2] = rotl(a[2], 27); + a[3] = rotl(a[3], 30); + } + + // Helpers + + private int bytesToIntBig(byte[] in, int off) + { + return ((in[off++]) << 24) | + ((in[off++] & 0xff) << 16) | + ((in[off++] & 0xff) << 8) | + (in[off ] & 0xff); + } + + private void intToBytesBig(int x, byte[] out, int off) + { + out[off++] = (byte)(x >>> 24); + out[off++] = (byte)(x >>> 16); + out[off++] = (byte)(x >>> 8); + out[off ] = (byte)x; + } + + private int rotl(int x, int y) + { + return (x << y) | (x >>> (32-y)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/NullEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/NullEngine.java new file mode 100644 index 000000000..f7b94e8d2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/NullEngine.java @@ -0,0 +1,84 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; + +/** + * The no-op engine that just copies bytes through, irrespective of whether encrypting and decrypting. + * Provided for the sake of completeness. + */ +public class NullEngine implements BlockCipher +{ + private boolean initialised; + protected static final int BLOCK_SIZE = 1; + + /** + * Standard constructor. + */ + public NullEngine() + { + super(); + } + + /* (non-Javadoc) + * @see org.bouncycastle.crypto.BlockCipher#init(boolean, org.bouncycastle.crypto.CipherParameters) + */ + public void init(boolean forEncryption, CipherParameters params) throws IllegalArgumentException + { + // we don't mind any parameters that may come in + this.initialised = true; + } + + /* (non-Javadoc) + * @see org.bouncycastle.crypto.BlockCipher#getAlgorithmName() + */ + public String getAlgorithmName() + { + return "Null"; + } + + /* (non-Javadoc) + * @see org.bouncycastle.crypto.BlockCipher#getBlockSize() + */ + public int getBlockSize() + { + return BLOCK_SIZE; + } + + /* (non-Javadoc) + * @see org.bouncycastle.crypto.BlockCipher#processBlock(byte[], int, byte[], int) + */ + public int processBlock(byte[] in, int inOff, byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + if (!initialised) + { + throw new IllegalStateException("Null engine not initialised"); + } + if ((inOff + BLOCK_SIZE) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + BLOCK_SIZE) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + for (int i = 0; i < BLOCK_SIZE; ++i) + { + out[outOff + i] = in[inOff + i]; + } + + return BLOCK_SIZE; + } + + /* (non-Javadoc) + * @see org.bouncycastle.crypto.BlockCipher#reset() + */ + public void reset() + { + // nothing needs to be done + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/RC2Engine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/RC2Engine.java new file mode 100644 index 000000000..1f364de6c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/RC2Engine.java @@ -0,0 +1,316 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.RC2Parameters; + +/** + * an implementation of RC2 as described in RFC 2268 + * "A Description of the RC2(r) Encryption Algorithm" R. Rivest. + */ +public class RC2Engine + implements BlockCipher +{ + // + // the values we use for key expansion (based on the digits of PI) + // + private static byte[] piTable = + { + (byte)0xd9, (byte)0x78, (byte)0xf9, (byte)0xc4, (byte)0x19, (byte)0xdd, (byte)0xb5, (byte)0xed, + (byte)0x28, (byte)0xe9, (byte)0xfd, (byte)0x79, (byte)0x4a, (byte)0xa0, (byte)0xd8, (byte)0x9d, + (byte)0xc6, (byte)0x7e, (byte)0x37, (byte)0x83, (byte)0x2b, (byte)0x76, (byte)0x53, (byte)0x8e, + (byte)0x62, (byte)0x4c, (byte)0x64, (byte)0x88, (byte)0x44, (byte)0x8b, (byte)0xfb, (byte)0xa2, + (byte)0x17, (byte)0x9a, (byte)0x59, (byte)0xf5, (byte)0x87, (byte)0xb3, (byte)0x4f, (byte)0x13, + (byte)0x61, (byte)0x45, (byte)0x6d, (byte)0x8d, (byte)0x9, (byte)0x81, (byte)0x7d, (byte)0x32, + (byte)0xbd, (byte)0x8f, (byte)0x40, (byte)0xeb, (byte)0x86, (byte)0xb7, (byte)0x7b, (byte)0xb, + (byte)0xf0, (byte)0x95, (byte)0x21, (byte)0x22, (byte)0x5c, (byte)0x6b, (byte)0x4e, (byte)0x82, + (byte)0x54, (byte)0xd6, (byte)0x65, (byte)0x93, (byte)0xce, (byte)0x60, (byte)0xb2, (byte)0x1c, + (byte)0x73, (byte)0x56, (byte)0xc0, (byte)0x14, (byte)0xa7, (byte)0x8c, (byte)0xf1, (byte)0xdc, + (byte)0x12, (byte)0x75, (byte)0xca, (byte)0x1f, (byte)0x3b, (byte)0xbe, (byte)0xe4, (byte)0xd1, + (byte)0x42, (byte)0x3d, (byte)0xd4, (byte)0x30, (byte)0xa3, (byte)0x3c, (byte)0xb6, (byte)0x26, + (byte)0x6f, (byte)0xbf, (byte)0xe, (byte)0xda, (byte)0x46, (byte)0x69, (byte)0x7, (byte)0x57, + (byte)0x27, (byte)0xf2, (byte)0x1d, (byte)0x9b, (byte)0xbc, (byte)0x94, (byte)0x43, (byte)0x3, + (byte)0xf8, (byte)0x11, (byte)0xc7, (byte)0xf6, (byte)0x90, (byte)0xef, (byte)0x3e, (byte)0xe7, + (byte)0x6, (byte)0xc3, (byte)0xd5, (byte)0x2f, (byte)0xc8, (byte)0x66, (byte)0x1e, (byte)0xd7, + (byte)0x8, (byte)0xe8, (byte)0xea, (byte)0xde, (byte)0x80, (byte)0x52, (byte)0xee, (byte)0xf7, + (byte)0x84, (byte)0xaa, (byte)0x72, (byte)0xac, (byte)0x35, (byte)0x4d, (byte)0x6a, (byte)0x2a, + (byte)0x96, (byte)0x1a, (byte)0xd2, (byte)0x71, (byte)0x5a, (byte)0x15, (byte)0x49, (byte)0x74, + (byte)0x4b, (byte)0x9f, (byte)0xd0, (byte)0x5e, (byte)0x4, (byte)0x18, (byte)0xa4, (byte)0xec, + (byte)0xc2, (byte)0xe0, (byte)0x41, (byte)0x6e, (byte)0xf, (byte)0x51, (byte)0xcb, (byte)0xcc, + (byte)0x24, (byte)0x91, (byte)0xaf, (byte)0x50, (byte)0xa1, (byte)0xf4, (byte)0x70, (byte)0x39, + (byte)0x99, (byte)0x7c, (byte)0x3a, (byte)0x85, (byte)0x23, (byte)0xb8, (byte)0xb4, (byte)0x7a, + (byte)0xfc, (byte)0x2, (byte)0x36, (byte)0x5b, (byte)0x25, (byte)0x55, (byte)0x97, (byte)0x31, + (byte)0x2d, (byte)0x5d, (byte)0xfa, (byte)0x98, (byte)0xe3, (byte)0x8a, (byte)0x92, (byte)0xae, + (byte)0x5, (byte)0xdf, (byte)0x29, (byte)0x10, (byte)0x67, (byte)0x6c, (byte)0xba, (byte)0xc9, + (byte)0xd3, (byte)0x0, (byte)0xe6, (byte)0xcf, (byte)0xe1, (byte)0x9e, (byte)0xa8, (byte)0x2c, + (byte)0x63, (byte)0x16, (byte)0x1, (byte)0x3f, (byte)0x58, (byte)0xe2, (byte)0x89, (byte)0xa9, + (byte)0xd, (byte)0x38, (byte)0x34, (byte)0x1b, (byte)0xab, (byte)0x33, (byte)0xff, (byte)0xb0, + (byte)0xbb, (byte)0x48, (byte)0xc, (byte)0x5f, (byte)0xb9, (byte)0xb1, (byte)0xcd, (byte)0x2e, + (byte)0xc5, (byte)0xf3, (byte)0xdb, (byte)0x47, (byte)0xe5, (byte)0xa5, (byte)0x9c, (byte)0x77, + (byte)0xa, (byte)0xa6, (byte)0x20, (byte)0x68, (byte)0xfe, (byte)0x7f, (byte)0xc1, (byte)0xad + }; + + private static final int BLOCK_SIZE = 8; + + private int[] workingKey; + private boolean encrypting; + + private int[] generateWorkingKey( + byte[] key, + int bits) + { + int x; + int[] xKey = new int[128]; + + for (int i = 0; i != key.length; i++) + { + xKey[i] = key[i] & 0xff; + } + + // Phase 1: Expand input key to 128 bytes + int len = key.length; + + if (len < 128) + { + int index = 0; + + x = xKey[len - 1]; + + do + { + x = piTable[(x + xKey[index++]) & 255] & 0xff; + xKey[len++] = x; + } + while (len < 128); + } + + // Phase 2 - reduce effective key size to "bits" + len = (bits + 7) >> 3; + x = piTable[xKey[128 - len] & (255 >> (7 & -bits))] & 0xff; + xKey[128 - len] = x; + + for (int i = 128 - len - 1; i >= 0; i--) + { + x = piTable[x ^ xKey[i + len]] & 0xff; + xKey[i] = x; + } + + // Phase 3 - copy to newKey in little-endian order + int[] newKey = new int[64]; + + for (int i = 0; i != newKey.length; i++) + { + newKey[i] = (xKey[2 * i] + (xKey[2 * i + 1] << 8)); + } + + return newKey; + } + + /** + * initialise a RC2 cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + this.encrypting = encrypting; + + if (params instanceof RC2Parameters) + { + RC2Parameters param = (RC2Parameters)params; + + workingKey = generateWorkingKey(param.getKey(), + param.getEffectiveKeyBits()); + } + else if (params instanceof KeyParameter) + { + byte[] key = ((KeyParameter)params).getKey(); + + workingKey = generateWorkingKey(key, key.length * 8); + } + else + { + throw new IllegalArgumentException("invalid parameter passed to RC2 init - " + params.getClass().getName()); + } + + } + + public void reset() + { + } + + public String getAlgorithmName() + { + return "RC2"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public final int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("RC2 engine not initialised"); + } + + if ((inOff + BLOCK_SIZE) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + BLOCK_SIZE) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (encrypting) + { + encryptBlock(in, inOff, out, outOff); + } + else + { + decryptBlock(in, inOff, out, outOff); + } + + return BLOCK_SIZE; + } + + /** + * return the result rotating the 16 bit number in x left by y + */ + private int rotateWordLeft( + int x, + int y) + { + x &= 0xffff; + return (x << y) | (x >> (16 - y)); + } + + private void encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int x76, x54, x32, x10; + + x76 = ((in[inOff + 7] & 0xff) << 8) + (in[inOff + 6] & 0xff); + x54 = ((in[inOff + 5] & 0xff) << 8) + (in[inOff + 4] & 0xff); + x32 = ((in[inOff + 3] & 0xff) << 8) + (in[inOff + 2] & 0xff); + x10 = ((in[inOff + 1] & 0xff) << 8) + (in[inOff + 0] & 0xff); + + for (int i = 0; i <= 16; i += 4) + { + x10 = rotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + workingKey[i ], 1); + x32 = rotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + workingKey[i+1], 2); + x54 = rotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + workingKey[i+2], 3); + x76 = rotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + workingKey[i+3], 5); + } + + x10 += workingKey[x76 & 63]; + x32 += workingKey[x10 & 63]; + x54 += workingKey[x32 & 63]; + x76 += workingKey[x54 & 63]; + + for (int i = 20; i <= 40; i += 4) + { + x10 = rotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + workingKey[i ], 1); + x32 = rotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + workingKey[i+1], 2); + x54 = rotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + workingKey[i+2], 3); + x76 = rotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + workingKey[i+3], 5); + } + + x10 += workingKey[x76 & 63]; + x32 += workingKey[x10 & 63]; + x54 += workingKey[x32 & 63]; + x76 += workingKey[x54 & 63]; + + for (int i = 44; i < 64; i += 4) + { + x10 = rotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + workingKey[i ], 1); + x32 = rotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + workingKey[i+1], 2); + x54 = rotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + workingKey[i+2], 3); + x76 = rotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + workingKey[i+3], 5); + } + + out[outOff + 0] = (byte)x10; + out[outOff + 1] = (byte)(x10 >> 8); + out[outOff + 2] = (byte)x32; + out[outOff + 3] = (byte)(x32 >> 8); + out[outOff + 4] = (byte)x54; + out[outOff + 5] = (byte)(x54 >> 8); + out[outOff + 6] = (byte)x76; + out[outOff + 7] = (byte)(x76 >> 8); + } + + private void decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int x76, x54, x32, x10; + + x76 = ((in[inOff + 7] & 0xff) << 8) + (in[inOff + 6] & 0xff); + x54 = ((in[inOff + 5] & 0xff) << 8) + (in[inOff + 4] & 0xff); + x32 = ((in[inOff + 3] & 0xff) << 8) + (in[inOff + 2] & 0xff); + x10 = ((in[inOff + 1] & 0xff) << 8) + (in[inOff + 0] & 0xff); + + for (int i = 60; i >= 44; i -= 4) + { + x76 = rotateWordLeft(x76, 11) - ((x10 & ~x54) + (x32 & x54) + workingKey[i+3]); + x54 = rotateWordLeft(x54, 13) - ((x76 & ~x32) + (x10 & x32) + workingKey[i+2]); + x32 = rotateWordLeft(x32, 14) - ((x54 & ~x10) + (x76 & x10) + workingKey[i+1]); + x10 = rotateWordLeft(x10, 15) - ((x32 & ~x76) + (x54 & x76) + workingKey[i ]); + } + + x76 -= workingKey[x54 & 63]; + x54 -= workingKey[x32 & 63]; + x32 -= workingKey[x10 & 63]; + x10 -= workingKey[x76 & 63]; + + for (int i = 40; i >= 20; i -= 4) + { + x76 = rotateWordLeft(x76, 11) - ((x10 & ~x54) + (x32 & x54) + workingKey[i+3]); + x54 = rotateWordLeft(x54, 13) - ((x76 & ~x32) + (x10 & x32) + workingKey[i+2]); + x32 = rotateWordLeft(x32, 14) - ((x54 & ~x10) + (x76 & x10) + workingKey[i+1]); + x10 = rotateWordLeft(x10, 15) - ((x32 & ~x76) + (x54 & x76) + workingKey[i ]); + } + + x76 -= workingKey[x54 & 63]; + x54 -= workingKey[x32 & 63]; + x32 -= workingKey[x10 & 63]; + x10 -= workingKey[x76 & 63]; + + for (int i = 16; i >= 0; i -= 4) + { + x76 = rotateWordLeft(x76, 11) - ((x10 & ~x54) + (x32 & x54) + workingKey[i+3]); + x54 = rotateWordLeft(x54, 13) - ((x76 & ~x32) + (x10 & x32) + workingKey[i+2]); + x32 = rotateWordLeft(x32, 14) - ((x54 & ~x10) + (x76 & x10) + workingKey[i+1]); + x10 = rotateWordLeft(x10, 15) - ((x32 & ~x76) + (x54 & x76) + workingKey[i ]); + } + + out[outOff + 0] = (byte)x10; + out[outOff + 1] = (byte)(x10 >> 8); + out[outOff + 2] = (byte)x32; + out[outOff + 3] = (byte)(x32 >> 8); + out[outOff + 4] = (byte)x54; + out[outOff + 5] = (byte)(x54 >> 8); + out[outOff + 6] = (byte)x76; + out[outOff + 7] = (byte)(x76 >> 8); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/RC2WrapEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/RC2WrapEngine.java new file mode 100644 index 000000000..42819ed73 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/RC2WrapEngine.java @@ -0,0 +1,383 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; +import com.google.bitcoin.bouncycastle.crypto.Wrapper; +import com.google.bitcoin.bouncycastle.crypto.digests.SHA1Digest; +import com.google.bitcoin.bouncycastle.crypto.modes.CBCBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.util.Arrays; + +/** + * Wrap keys according to RFC 3217 - RC2 mechanism + */ +public class RC2WrapEngine + implements Wrapper +{ + /** Field engine */ + private CBCBlockCipher engine; + + /** Field param */ + private CipherParameters param; + + /** Field paramPlusIV */ + private ParametersWithIV paramPlusIV; + + /** Field iv */ + private byte[] iv; + + /** Field forWrapping */ + private boolean forWrapping; + + private SecureRandom sr; + + /** Field IV2 */ + private static final byte[] IV2 = { (byte) 0x4a, (byte) 0xdd, (byte) 0xa2, + (byte) 0x2c, (byte) 0x79, (byte) 0xe8, + (byte) 0x21, (byte) 0x05 }; + + // + // checksum digest + // + Digest sha1 = new SHA1Digest(); + byte[] digest = new byte[20]; + + /** + * Method init + * + * @param forWrapping + * @param param + */ + public void init(boolean forWrapping, CipherParameters param) + { + this.forWrapping = forWrapping; + this.engine = new CBCBlockCipher(new RC2Engine()); + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom pWithR = (ParametersWithRandom)param; + sr = pWithR.getRandom(); + param = pWithR.getParameters(); + } + else + { + sr = new SecureRandom(); + } + + if (param instanceof ParametersWithIV) + { + this.paramPlusIV = (ParametersWithIV)param; + this.iv = this.paramPlusIV.getIV(); + this.param = this.paramPlusIV.getParameters(); + + if (this.forWrapping) + { + if ((this.iv == null) || (this.iv.length != 8)) + { + throw new IllegalArgumentException("IV is not 8 octets"); + } + } + else + { + throw new IllegalArgumentException( + "You should not supply an IV for unwrapping"); + } + } + else + { + this.param = param; + + if (this.forWrapping) + { + + // Hm, we have no IV but we want to wrap ?!? + // well, then we have to create our own IV. + this.iv = new byte[8]; + + sr.nextBytes(iv); + + this.paramPlusIV = new ParametersWithIV(this.param, this.iv); + } + } + + } + + /** + * Method getAlgorithmName + * + * @return the algorithm name "RC2". + */ + public String getAlgorithmName() + { + return "RC2"; + } + + /** + * Method wrap + * + * @param in + * @param inOff + * @param inLen + * @return the wrapped bytes. + */ + public byte[] wrap(byte[] in, int inOff, int inLen) + { + + if (!forWrapping) + { + throw new IllegalStateException("Not initialized for wrapping"); + } + + int length = inLen + 1; + if ((length % 8) != 0) + { + length += 8 - (length % 8); + } + + byte keyToBeWrapped[] = new byte[length]; + + keyToBeWrapped[0] = (byte)inLen; + System.arraycopy(in, inOff, keyToBeWrapped, 1, inLen); + + byte[] pad = new byte[keyToBeWrapped.length - inLen - 1]; + + if (pad.length > 0) + { + sr.nextBytes(pad); + System.arraycopy(pad, 0, keyToBeWrapped, inLen + 1, pad.length); + } + + // Compute the CMS Key Checksum, (section 5.6.1), call this CKS. + byte[] CKS = calculateCMSKeyChecksum(keyToBeWrapped); + + // Let WKCKS = WK || CKS where || is concatenation. + byte[] WKCKS = new byte[keyToBeWrapped.length + CKS.length]; + + System.arraycopy(keyToBeWrapped, 0, WKCKS, 0, keyToBeWrapped.length); + System.arraycopy(CKS, 0, WKCKS, keyToBeWrapped.length, CKS.length); + + // Encrypt WKCKS in CBC mode using KEK as the key and IV as the + // initialization vector. Call the results TEMP1. + byte TEMP1[] = new byte[WKCKS.length]; + + System.arraycopy(WKCKS, 0, TEMP1, 0, WKCKS.length); + + int noOfBlocks = WKCKS.length / engine.getBlockSize(); + int extraBytes = WKCKS.length % engine.getBlockSize(); + + if (extraBytes != 0) + { + throw new IllegalStateException("Not multiple of block length"); + } + + engine.init(true, paramPlusIV); + + for (int i = 0; i < noOfBlocks; i++) + { + int currentBytePos = i * engine.getBlockSize(); + + engine.processBlock(TEMP1, currentBytePos, TEMP1, currentBytePos); + } + + // Left TEMP2 = IV || TEMP1. + byte[] TEMP2 = new byte[this.iv.length + TEMP1.length]; + + System.arraycopy(this.iv, 0, TEMP2, 0, this.iv.length); + System.arraycopy(TEMP1, 0, TEMP2, this.iv.length, TEMP1.length); + + // Reverse the order of the octets in TEMP2 and call the result TEMP3. + byte[] TEMP3 = new byte[TEMP2.length]; + + for (int i = 0; i < TEMP2.length; i++) + { + TEMP3[i] = TEMP2[TEMP2.length - (i + 1)]; + } + + // Encrypt TEMP3 in CBC mode using the KEK and an initialization vector + // of 0x 4a dd a2 2c 79 e8 21 05. The resulting cipher text is the + // desired + // result. It is 40 octets long if a 168 bit key is being wrapped. + ParametersWithIV param2 = new ParametersWithIV(this.param, IV2); + + this.engine.init(true, param2); + + for (int i = 0; i < noOfBlocks + 1; i++) + { + int currentBytePos = i * engine.getBlockSize(); + + engine.processBlock(TEMP3, currentBytePos, TEMP3, currentBytePos); + } + + return TEMP3; + } + + /** + * Method unwrap + * + * @param in + * @param inOff + * @param inLen + * @return the unwrapped bytes. + * @throws InvalidCipherTextException + */ + public byte[] unwrap(byte[] in, int inOff, int inLen) + throws InvalidCipherTextException + { + + if (forWrapping) + { + throw new IllegalStateException("Not set for unwrapping"); + } + + if (in == null) + { + throw new InvalidCipherTextException("Null pointer as ciphertext"); + } + + if (inLen % engine.getBlockSize() != 0) + { + throw new InvalidCipherTextException("Ciphertext not multiple of " + + engine.getBlockSize()); + } + + /* + * // Check if the length of the cipher text is reasonable given the key // + * type. It must be 40 bytes for a 168 bit key and either 32, 40, or // + * 48 bytes for a 128, 192, or 256 bit key. If the length is not + * supported // or inconsistent with the algorithm for which the key is + * intended, // return error. // // we do not accept 168 bit keys. it + * has to be 192 bit. int lengthA = (estimatedKeyLengthInBit / 8) + 16; + * int lengthB = estimatedKeyLengthInBit % 8; + * + * if ((lengthA != keyToBeUnwrapped.length) || (lengthB != 0)) { throw + * new XMLSecurityException("empty"); } + */ + + // Decrypt the cipher text with TRIPLedeS in CBC mode using the KEK + // and an initialization vector (IV) of 0x4adda22c79e82105. Call the + // output TEMP3. + ParametersWithIV param2 = new ParametersWithIV(this.param, IV2); + + this.engine.init(false, param2); + + byte TEMP3[] = new byte[inLen]; + + System.arraycopy(in, inOff, TEMP3, 0, inLen); + + for (int i = 0; i < (TEMP3.length / engine.getBlockSize()); i++) + { + int currentBytePos = i * engine.getBlockSize(); + + engine.processBlock(TEMP3, currentBytePos, TEMP3, currentBytePos); + } + + // Reverse the order of the octets in TEMP3 and call the result TEMP2. + byte[] TEMP2 = new byte[TEMP3.length]; + + for (int i = 0; i < TEMP3.length; i++) + { + TEMP2[i] = TEMP3[TEMP3.length - (i + 1)]; + } + + // Decompose TEMP2 into IV, the first 8 octets, and TEMP1, the remaining + // octets. + this.iv = new byte[8]; + + byte[] TEMP1 = new byte[TEMP2.length - 8]; + + System.arraycopy(TEMP2, 0, this.iv, 0, 8); + System.arraycopy(TEMP2, 8, TEMP1, 0, TEMP2.length - 8); + + // Decrypt TEMP1 using TRIPLedeS in CBC mode using the KEK and the IV + // found in the previous step. Call the result WKCKS. + this.paramPlusIV = new ParametersWithIV(this.param, this.iv); + + this.engine.init(false, this.paramPlusIV); + + byte[] LCEKPADICV = new byte[TEMP1.length]; + + System.arraycopy(TEMP1, 0, LCEKPADICV, 0, TEMP1.length); + + for (int i = 0; i < (LCEKPADICV.length / engine.getBlockSize()); i++) + { + int currentBytePos = i * engine.getBlockSize(); + + engine.processBlock(LCEKPADICV, currentBytePos, LCEKPADICV, + currentBytePos); + } + + // Decompose LCEKPADICV. CKS is the last 8 octets and WK, the wrapped + // key, are + // those octets before the CKS. + byte[] result = new byte[LCEKPADICV.length - 8]; + byte[] CKStoBeVerified = new byte[8]; + + System.arraycopy(LCEKPADICV, 0, result, 0, LCEKPADICV.length - 8); + System.arraycopy(LCEKPADICV, LCEKPADICV.length - 8, CKStoBeVerified, 0, + 8); + + // Calculate a CMS Key Checksum, (section 5.6.1), over the WK and + // compare + // with the CKS extracted in the above step. If they are not equal, + // return error. + if (!checkCMSKeyChecksum(result, CKStoBeVerified)) + { + throw new InvalidCipherTextException( + "Checksum inside ciphertext is corrupted"); + } + + if ((result.length - ((result[0] & 0xff) + 1)) > 7) + { + throw new InvalidCipherTextException("too many pad bytes (" + + (result.length - ((result[0] & 0xff) + 1)) + ")"); + } + + // CEK is the wrapped key, now extracted for use in data decryption. + byte[] CEK = new byte[result[0]]; + System.arraycopy(result, 1, CEK, 0, CEK.length); + return CEK; + } + + /** + * Some key wrap algorithms make use of the Key Checksum defined + * in CMS [CMS-Algorithms]. This is used to provide an integrity + * check value for the key being wrapped. The algorithm is + * + * - Compute the 20 octet SHA-1 hash on the key being wrapped. + * - Use the first 8 octets of this hash as the checksum value. + * + * @param key + * @return + * @throws RuntimeException + * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + */ + private byte[] calculateCMSKeyChecksum( + byte[] key) + { + byte[] result = new byte[8]; + + sha1.update(key, 0, key.length); + sha1.doFinal(digest, 0); + + System.arraycopy(digest, 0, result, 0, 8); + + return result; + } + + /** + * @param key + * @param checksum + * @return + * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + */ + private boolean checkCMSKeyChecksum( + byte[] key, + byte[] checksum) + { + return Arrays.constantTimeAreEqual(calculateCMSKeyChecksum(key), checksum); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/RC4Engine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/RC4Engine.java new file mode 100644 index 000000000..bb1a1ac3f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/RC4Engine.java @@ -0,0 +1,143 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.StreamCipher; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +public class RC4Engine implements StreamCipher +{ + private final static int STATE_LENGTH = 256; + + /* + * variables to hold the state of the RC4 engine + * during encryption and decryption + */ + + private byte[] engineState = null; + private int x = 0; + private int y = 0; + private byte[] workingKey = null; + + /** + * initialise a RC4 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params + ) + { + if (params instanceof KeyParameter) + { + /* + * RC4 encryption and decryption is completely + * symmetrical, so the 'forEncryption' is + * irrelevant. + */ + workingKey = ((KeyParameter)params).getKey(); + setKey(workingKey); + + return; + } + + throw new IllegalArgumentException("invalid parameter passed to RC4 init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "RC4"; + } + + public byte returnByte(byte in) + { + x = (x + 1) & 0xff; + y = (engineState[x] + y) & 0xff; + + // swap + byte tmp = engineState[x]; + engineState[x] = engineState[y]; + engineState[y] = tmp; + + // xor + return (byte)(in ^ engineState[(engineState[x] + engineState[y]) & 0xff]); + } + + public void processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + { + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + for (int i = 0; i < len ; i++) + { + x = (x + 1) & 0xff; + y = (engineState[x] + y) & 0xff; + + // swap + byte tmp = engineState[x]; + engineState[x] = engineState[y]; + engineState[y] = tmp; + + // xor + out[i+outOff] = (byte)(in[i + inOff] + ^ engineState[(engineState[x] + engineState[y]) & 0xff]); + } + } + + public void reset() + { + setKey(workingKey); + } + + // Private implementation + + private void setKey(byte[] keyBytes) + { + workingKey = keyBytes; + + // System.out.println("the key length is ; "+ workingKey.length); + + x = 0; + y = 0; + + if (engineState == null) + { + engineState = new byte[STATE_LENGTH]; + } + + // reset the state of the engine + for (int i=0; i < STATE_LENGTH; i++) + { + engineState[i] = (byte)i; + } + + int i1 = 0; + int i2 = 0; + + for (int i=0; i < STATE_LENGTH; i++) + { + i2 = ((keyBytes[i1] & 0xff) + engineState[i] + i2) & 0xff; + // do the byte-swap inline + byte tmp = engineState[i]; + engineState[i] = engineState[i2]; + engineState[i2] = tmp; + i1 = (i1+1) % keyBytes.length; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/RC532Engine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/RC532Engine.java new file mode 100644 index 000000000..1d8fa371f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/RC532Engine.java @@ -0,0 +1,287 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.RC5Parameters; + +/** + * The specification for RC5 came from the RC5 Encryption Algorithm + * publication in RSA CryptoBytes, Spring of 1995. + * http://www.rsasecurity.com/rsalabs/cryptobytes. + *

+ * This implementation has a word size of 32 bits. + *

+ * Implementation courtesy of Tito Pena. + */ +public class RC532Engine + implements BlockCipher +{ + /* + * the number of rounds to perform + */ + private int _noRounds; + + /* + * the expanded key array of size 2*(rounds + 1) + */ + private int _S[]; + + /* + * our "magic constants" for 32 32 + * + * Pw = Odd((e-2) * 2^wordsize) + * Qw = Odd((o-2) * 2^wordsize) + * + * where e is the base of natural logarithms (2.718281828...) + * and o is the golden ratio (1.61803398...) + */ + private static final int P32 = 0xb7e15163; + private static final int Q32 = 0x9e3779b9; + + private boolean forEncryption; + + /** + * Create an instance of the RC5 encryption algorithm + * and set some defaults + */ + public RC532Engine() + { + _noRounds = 12; // the default + _S = null; + } + + public String getAlgorithmName() + { + return "RC5-32"; + } + + public int getBlockSize() + { + return 2 * 4; + } + + /** + * initialise a RC5-32 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (params instanceof RC5Parameters) + { + RC5Parameters p = (RC5Parameters)params; + + _noRounds = p.getRounds(); + + setKey(p.getKey()); + } + else if (params instanceof KeyParameter) + { + KeyParameter p = (KeyParameter)params; + + setKey(p.getKey()); + } + else + { + throw new IllegalArgumentException("invalid parameter passed to RC532 init - " + params.getClass().getName()); + } + + this.forEncryption = forEncryption; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + return (forEncryption) ? encryptBlock(in, inOff, out, outOff) + : decryptBlock(in, inOff, out, outOff); + } + + public void reset() + { + } + + /** + * Re-key the cipher. + *

+ * @param key the key to be used + */ + private void setKey( + byte[] key) + { + // + // KEY EXPANSION: + // + // There are 3 phases to the key expansion. + // + // Phase 1: + // Copy the secret key K[0...b-1] into an array L[0..c-1] of + // c = ceil(b/u), where u = 32/8 in little-endian order. + // In other words, we fill up L using u consecutive key bytes + // of K. Any unfilled byte positions in L are zeroed. In the + // case that b = c = 0, set c = 1 and L[0] = 0. + // + int[] L = new int[(key.length + (4 - 1)) / 4]; + + for (int i = 0; i != key.length; i++) + { + L[i / 4] += (key[i] & 0xff) << (8 * (i % 4)); + } + + // + // Phase 2: + // Initialize S to a particular fixed pseudo-random bit pattern + // using an arithmetic progression modulo 2^wordsize determined + // by the magic numbers, Pw & Qw. + // + _S = new int[2*(_noRounds + 1)]; + + _S[0] = P32; + for (int i=1; i < _S.length; i++) + { + _S[i] = (_S[i-1] + Q32); + } + + // + // Phase 3: + // Mix in the user's secret key in 3 passes over the arrays S & L. + // The max of the arrays sizes is used as the loop control + // + int iter; + + if (L.length > _S.length) + { + iter = 3 * L.length; + } + else + { + iter = 3 * _S.length; + } + + int A = 0, B = 0; + int i = 0, j = 0; + + for (int k = 0; k < iter; k++) + { + A = _S[i] = rotateLeft(_S[i] + A + B, 3); + B = L[j] = rotateLeft(L[j] + A + B, A+B); + i = (i+1) % _S.length; + j = (j+1) % L.length; + } + } + + /** + * Encrypt the given block starting at the given offset and place + * the result in the provided buffer starting at the given offset. + *

+ * @param in in byte buffer containing data to encrypt + * @param inOff offset into src buffer + * @param out out buffer where encrypted data is written + * @param outOff offset into out buffer + */ + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int A = bytesToWord(in, inOff) + _S[0]; + int B = bytesToWord(in, inOff + 4) + _S[1]; + + for (int i = 1; i <= _noRounds; i++) + { + A = rotateLeft(A ^ B, B) + _S[2*i]; + B = rotateLeft(B ^ A, A) + _S[2*i+1]; + } + + wordToBytes(A, out, outOff); + wordToBytes(B, out, outOff + 4); + + return 2 * 4; + } + + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int A = bytesToWord(in, inOff); + int B = bytesToWord(in, inOff + 4); + + for (int i = _noRounds; i >= 1; i--) + { + B = rotateRight(B - _S[2*i+1], A) ^ A; + A = rotateRight(A - _S[2*i], B) ^ B; + } + + wordToBytes(A - _S[0], out, outOff); + wordToBytes(B - _S[1], out, outOff + 4); + + return 2 * 4; + } + + + ////////////////////////////////////////////////////////////// + // + // PRIVATE Helper Methods + // + ////////////////////////////////////////////////////////////// + + /** + * Perform a left "spin" of the word. The rotation of the given + * word x is rotated left by y bits. + * Only the lg(32) low-order bits of y + * are used to determine the rotation amount. Here it is + * assumed that the wordsize used is a power of 2. + *

+ * @param x word to rotate + * @param y number of bits to rotate % 32 + */ + private int rotateLeft(int x, int y) + { + return ((x << (y & (32-1))) | (x >>> (32 - (y & (32-1))))); + } + + /** + * Perform a right "spin" of the word. The rotation of the given + * word x is rotated left by y bits. + * Only the lg(32) low-order bits of y + * are used to determine the rotation amount. Here it is + * assumed that the wordsize used is a power of 2. + *

+ * @param x word to rotate + * @param y number of bits to rotate % 32 + */ + private int rotateRight(int x, int y) + { + return ((x >>> (y & (32-1))) | (x << (32 - (y & (32-1))))); + } + + private int bytesToWord( + byte[] src, + int srcOff) + { + return (src[srcOff] & 0xff) | ((src[srcOff + 1] & 0xff) << 8) + | ((src[srcOff + 2] & 0xff) << 16) | ((src[srcOff + 3] & 0xff) << 24); + } + + private void wordToBytes( + int word, + byte[] dst, + int dstOff) + { + dst[dstOff] = (byte)word; + dst[dstOff + 1] = (byte)(word >> 8); + dst[dstOff + 2] = (byte)(word >> 16); + dst[dstOff + 3] = (byte)(word >> 24); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/RC564Engine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/RC564Engine.java new file mode 100644 index 000000000..01d7c20c5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/RC564Engine.java @@ -0,0 +1,288 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.params.RC5Parameters; + +/** + * The specification for RC5 came from the RC5 Encryption Algorithm + * publication in RSA CryptoBytes, Spring of 1995. + * http://www.rsasecurity.com/rsalabs/cryptobytes. + *

+ * This implementation is set to work with a 64 bit word size. + *

+ * Implementation courtesy of Tito Pena. + */ +public class RC564Engine + implements BlockCipher +{ + private static final int wordSize = 64; + private static final int bytesPerWord = wordSize / 8; + + /* + * the number of rounds to perform + */ + private int _noRounds; + + /* + * the expanded key array of size 2*(rounds + 1) + */ + private long _S[]; + + /* + * our "magic constants" for wordSize 62 + * + * Pw = Odd((e-2) * 2^wordsize) + * Qw = Odd((o-2) * 2^wordsize) + * + * where e is the base of natural logarithms (2.718281828...) + * and o is the golden ratio (1.61803398...) + */ + private static final long P64 = 0xb7e151628aed2a6bL; + private static final long Q64 = 0x9e3779b97f4a7c15L; + + private boolean forEncryption; + + /** + * Create an instance of the RC5 encryption algorithm + * and set some defaults + */ + public RC564Engine() + { + _noRounds = 12; + _S = null; + } + + public String getAlgorithmName() + { + return "RC5-64"; + } + + public int getBlockSize() + { + return 2 * bytesPerWord; + } + + /** + * initialise a RC5-64 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (!(params instanceof RC5Parameters)) + { + throw new IllegalArgumentException("invalid parameter passed to RC564 init - " + params.getClass().getName()); + } + + RC5Parameters p = (RC5Parameters)params; + + this.forEncryption = forEncryption; + + _noRounds = p.getRounds(); + + setKey(p.getKey()); + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + return (forEncryption) ? encryptBlock(in, inOff, out, outOff) + : decryptBlock(in, inOff, out, outOff); + } + + public void reset() + { + } + + /** + * Re-key the cipher. + *

+ * @param key the key to be used + */ + private void setKey( + byte[] key) + { + // + // KEY EXPANSION: + // + // There are 3 phases to the key expansion. + // + // Phase 1: + // Copy the secret key K[0...b-1] into an array L[0..c-1] of + // c = ceil(b/u), where u = wordSize/8 in little-endian order. + // In other words, we fill up L using u consecutive key bytes + // of K. Any unfilled byte positions in L are zeroed. In the + // case that b = c = 0, set c = 1 and L[0] = 0. + // + long[] L = new long[(key.length + (bytesPerWord - 1)) / bytesPerWord]; + + for (int i = 0; i != key.length; i++) + { + L[i / bytesPerWord] += (long)(key[i] & 0xff) << (8 * (i % bytesPerWord)); + } + + // + // Phase 2: + // Initialize S to a particular fixed pseudo-random bit pattern + // using an arithmetic progression modulo 2^wordsize determined + // by the magic numbers, Pw & Qw. + // + _S = new long[2*(_noRounds + 1)]; + + _S[0] = P64; + for (int i=1; i < _S.length; i++) + { + _S[i] = (_S[i-1] + Q64); + } + + // + // Phase 3: + // Mix in the user's secret key in 3 passes over the arrays S & L. + // The max of the arrays sizes is used as the loop control + // + int iter; + + if (L.length > _S.length) + { + iter = 3 * L.length; + } + else + { + iter = 3 * _S.length; + } + + long A = 0, B = 0; + int i = 0, j = 0; + + for (int k = 0; k < iter; k++) + { + A = _S[i] = rotateLeft(_S[i] + A + B, 3); + B = L[j] = rotateLeft(L[j] + A + B, A+B); + i = (i+1) % _S.length; + j = (j+1) % L.length; + } + } + + /** + * Encrypt the given block starting at the given offset and place + * the result in the provided buffer starting at the given offset. + *

+ * @param in in byte buffer containing data to encrypt + * @param inOff offset into src buffer + * @param out out buffer where encrypted data is written + * @param outOff offset into out buffer + */ + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + long A = bytesToWord(in, inOff) + _S[0]; + long B = bytesToWord(in, inOff + bytesPerWord) + _S[1]; + + for (int i = 1; i <= _noRounds; i++) + { + A = rotateLeft(A ^ B, B) + _S[2*i]; + B = rotateLeft(B ^ A, A) + _S[2*i+1]; + } + + wordToBytes(A, out, outOff); + wordToBytes(B, out, outOff + bytesPerWord); + + return 2 * bytesPerWord; + } + + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + long A = bytesToWord(in, inOff); + long B = bytesToWord(in, inOff + bytesPerWord); + + for (int i = _noRounds; i >= 1; i--) + { + B = rotateRight(B - _S[2*i+1], A) ^ A; + A = rotateRight(A - _S[2*i], B) ^ B; + } + + wordToBytes(A - _S[0], out, outOff); + wordToBytes(B - _S[1], out, outOff + bytesPerWord); + + return 2 * bytesPerWord; + } + + + ////////////////////////////////////////////////////////////// + // + // PRIVATE Helper Methods + // + ////////////////////////////////////////////////////////////// + + /** + * Perform a left "spin" of the word. The rotation of the given + * word x is rotated left by y bits. + * Only the lg(wordSize) low-order bits of y + * are used to determine the rotation amount. Here it is + * assumed that the wordsize used is a power of 2. + *

+ * @param x word to rotate + * @param y number of bits to rotate % wordSize + */ + private long rotateLeft(long x, long y) + { + return ((x << (y & (wordSize-1))) | (x >>> (wordSize - (y & (wordSize-1))))); + } + + /** + * Perform a right "spin" of the word. The rotation of the given + * word x is rotated left by y bits. + * Only the lg(wordSize) low-order bits of y + * are used to determine the rotation amount. Here it is + * assumed that the wordsize used is a power of 2. + *

+ * @param x word to rotate + * @param y number of bits to rotate % wordSize + */ + private long rotateRight(long x, long y) + { + return ((x >>> (y & (wordSize-1))) | (x << (wordSize - (y & (wordSize-1))))); + } + + private long bytesToWord( + byte[] src, + int srcOff) + { + long word = 0; + + for (int i = bytesPerWord - 1; i >= 0; i--) + { + word = (word << 8) + (src[i + srcOff] & 0xff); + } + + return word; + } + + private void wordToBytes( + long word, + byte[] dst, + int dstOff) + { + for (int i = 0; i < bytesPerWord; i++) + { + dst[i + dstOff] = (byte)word; + word >>>= 8; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/RC6Engine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/RC6Engine.java new file mode 100644 index 000000000..4ac2f9f42 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/RC6Engine.java @@ -0,0 +1,362 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * An RC6 engine. + */ +public class RC6Engine + implements BlockCipher +{ + private static final int wordSize = 32; + private static final int bytesPerWord = wordSize / 8; + + /* + * the number of rounds to perform + */ + private static final int _noRounds = 20; + + /* + * the expanded key array of size 2*(rounds + 1) + */ + private int _S[]; + + /* + * our "magic constants" for wordSize 32 + * + * Pw = Odd((e-2) * 2^wordsize) + * Qw = Odd((o-2) * 2^wordsize) + * + * where e is the base of natural logarithms (2.718281828...) + * and o is the golden ratio (1.61803398...) + */ + private static final int P32 = 0xb7e15163; + private static final int Q32 = 0x9e3779b9; + + private static final int LGW = 5; // log2(32) + + private boolean forEncryption; + + /** + * Create an instance of the RC6 encryption algorithm + * and set some defaults + */ + public RC6Engine() + { + _S = null; + } + + public String getAlgorithmName() + { + return "RC6"; + } + + public int getBlockSize() + { + return 4 * bytesPerWord; + } + + /** + * initialise a RC5-32 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("invalid parameter passed to RC6 init - " + params.getClass().getName()); + } + + KeyParameter p = (KeyParameter)params; + this.forEncryption = forEncryption; + setKey(p.getKey()); + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int blockSize = getBlockSize(); + if (_S == null) + { + throw new IllegalStateException("RC6 engine not initialised"); + } + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + return (forEncryption) + ? encryptBlock(in, inOff, out, outOff) + : decryptBlock(in, inOff, out, outOff); + } + + public void reset() + { + } + + /** + * Re-key the cipher. + *

+ * @param inKey the key to be used + */ + private void setKey( + byte[] key) + { + + // + // KEY EXPANSION: + // + // There are 3 phases to the key expansion. + // + // Phase 1: + // Copy the secret key K[0...b-1] into an array L[0..c-1] of + // c = ceil(b/u), where u = wordSize/8 in little-endian order. + // In other words, we fill up L using u consecutive key bytes + // of K. Any unfilled byte positions in L are zeroed. In the + // case that b = c = 0, set c = 1 and L[0] = 0. + // + // compute number of dwords + int c = (key.length + (bytesPerWord - 1)) / bytesPerWord; + if (c == 0) + { + c = 1; + } + int[] L = new int[(key.length + bytesPerWord - 1) / bytesPerWord]; + + // load all key bytes into array of key dwords + for (int i = key.length - 1; i >= 0; i--) + { + L[i / bytesPerWord] = (L[i / bytesPerWord] << 8) + (key[i] & 0xff); + } + + // + // Phase 2: + // Key schedule is placed in a array of 2+2*ROUNDS+2 = 44 dwords. + // Initialize S to a particular fixed pseudo-random bit pattern + // using an arithmetic progression modulo 2^wordsize determined + // by the magic numbers, Pw & Qw. + // + _S = new int[2+2*_noRounds+2]; + + _S[0] = P32; + for (int i=1; i < _S.length; i++) + { + _S[i] = (_S[i-1] + Q32); + } + + // + // Phase 3: + // Mix in the user's secret key in 3 passes over the arrays S & L. + // The max of the arrays sizes is used as the loop control + // + int iter; + + if (L.length > _S.length) + { + iter = 3 * L.length; + } + else + { + iter = 3 * _S.length; + } + + int A = 0; + int B = 0; + int i = 0, j = 0; + + for (int k = 0; k < iter; k++) + { + A = _S[i] = rotateLeft(_S[i] + A + B, 3); + B = L[j] = rotateLeft(L[j] + A + B, A+B); + i = (i+1) % _S.length; + j = (j+1) % L.length; + } + } + + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + // load A,B,C and D registers from in. + int A = bytesToWord(in, inOff); + int B = bytesToWord(in, inOff + bytesPerWord); + int C = bytesToWord(in, inOff + bytesPerWord*2); + int D = bytesToWord(in, inOff + bytesPerWord*3); + + // Do pseudo-round #0: pre-whitening of B and D + B += _S[0]; + D += _S[1]; + + // perform round #1,#2 ... #ROUNDS of encryption + for (int i = 1; i <= _noRounds; i++) + { + int t = 0,u = 0; + + t = B*(2*B+1); + t = rotateLeft(t,5); + + u = D*(2*D+1); + u = rotateLeft(u,5); + + A ^= t; + A = rotateLeft(A,u); + A += _S[2*i]; + + C ^= u; + C = rotateLeft(C,t); + C += _S[2*i+1]; + + int temp = A; + A = B; + B = C; + C = D; + D = temp; + } + // do pseudo-round #(ROUNDS+1) : post-whitening of A and C + A += _S[2*_noRounds+2]; + C += _S[2*_noRounds+3]; + + // store A, B, C and D registers to out + wordToBytes(A, out, outOff); + wordToBytes(B, out, outOff + bytesPerWord); + wordToBytes(C, out, outOff + bytesPerWord*2); + wordToBytes(D, out, outOff + bytesPerWord*3); + + return 4 * bytesPerWord; + } + + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + // load A,B,C and D registers from out. + int A = bytesToWord(in, inOff); + int B = bytesToWord(in, inOff + bytesPerWord); + int C = bytesToWord(in, inOff + bytesPerWord*2); + int D = bytesToWord(in, inOff + bytesPerWord*3); + + // Undo pseudo-round #(ROUNDS+1) : post whitening of A and C + C -= _S[2*_noRounds+3]; + A -= _S[2*_noRounds+2]; + + // Undo round #ROUNDS, .., #2,#1 of encryption + for (int i = _noRounds; i >= 1; i--) + { + int t=0,u = 0; + + int temp = D; + D = C; + C = B; + B = A; + A = temp; + + t = B*(2*B+1); + t = rotateLeft(t, LGW); + + u = D*(2*D+1); + u = rotateLeft(u, LGW); + + C -= _S[2*i+1]; + C = rotateRight(C,t); + C ^= u; + + A -= _S[2*i]; + A = rotateRight(A,u); + A ^= t; + + } + // Undo pseudo-round #0: pre-whitening of B and D + D -= _S[1]; + B -= _S[0]; + + wordToBytes(A, out, outOff); + wordToBytes(B, out, outOff + bytesPerWord); + wordToBytes(C, out, outOff + bytesPerWord*2); + wordToBytes(D, out, outOff + bytesPerWord*3); + + return 4 * bytesPerWord; + } + + + ////////////////////////////////////////////////////////////// + // + // PRIVATE Helper Methods + // + ////////////////////////////////////////////////////////////// + + /** + * Perform a left "spin" of the word. The rotation of the given + * word x is rotated left by y bits. + * Only the lg(wordSize) low-order bits of y + * are used to determine the rotation amount. Here it is + * assumed that the wordsize used is 32. + *

+ * @param x word to rotate + * @param y number of bits to rotate % wordSize + */ + private int rotateLeft(int x, int y) + { + return (x << y) | (x >>> -y); + } + + /** + * Perform a right "spin" of the word. The rotation of the given + * word x is rotated left by y bits. + * Only the lg(wordSize) low-order bits of y + * are used to determine the rotation amount. Here it is + * assumed that the wordsize used is a power of 2. + *

+ * @param x word to rotate + * @param y number of bits to rotate % wordSize + */ + private int rotateRight(int x, int y) + { + return (x >>> y) | (x << -y); + } + + private int bytesToWord( + byte[] src, + int srcOff) + { + int word = 0; + + for (int i = bytesPerWord - 1; i >= 0; i--) + { + word = (word << 8) + (src[i + srcOff] & 0xff); + } + + return word; + } + + private void wordToBytes( + int word, + byte[] dst, + int dstOff) + { + for (int i = 0; i < bytesPerWord; i++) + { + dst[i + dstOff] = (byte)word; + word >>>= 8; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/RFC3211WrapEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/RFC3211WrapEngine.java new file mode 100644 index 000000000..f3f80f379 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/RFC3211WrapEngine.java @@ -0,0 +1,175 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; +import com.google.bitcoin.bouncycastle.crypto.Wrapper; +import com.google.bitcoin.bouncycastle.crypto.modes.CBCBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; + +import java.security.SecureRandom; + +/** + * an implementation of the RFC 3211 Key Wrap + * Specification. + */ +public class RFC3211WrapEngine + implements Wrapper +{ + private CBCBlockCipher engine; + private ParametersWithIV param; + private boolean forWrapping; + private SecureRandom rand; + + public RFC3211WrapEngine(BlockCipher engine) + { + this.engine = new CBCBlockCipher(engine); + } + + public void init( + boolean forWrapping, + CipherParameters param) + { + this.forWrapping = forWrapping; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)param; + + rand = p.getRandom(); + this.param = (ParametersWithIV)p.getParameters(); + } + else + { + if (forWrapping) + { + rand = new SecureRandom(); + } + + this.param = (ParametersWithIV)param; + } + } + + public String getAlgorithmName() + { + return engine.getUnderlyingCipher().getAlgorithmName() + "/RFC3211Wrap"; + } + + public byte[] wrap( + byte[] in, + int inOff, + int inLen) + { + if (!forWrapping) + { + throw new IllegalStateException("not set for wrapping"); + } + + engine.init(true, param); + + int blockSize = engine.getBlockSize(); + byte[] cekBlock; + + if (inLen + 4 < blockSize * 2) + { + cekBlock = new byte[blockSize * 2]; + } + else + { + cekBlock = new byte[(inLen + 4) % blockSize == 0 ? inLen + 4 : ((inLen + 4) / blockSize + 1) * blockSize]; + } + + cekBlock[0] = (byte)inLen; + cekBlock[1] = (byte)~in[inOff]; + cekBlock[2] = (byte)~in[inOff + 1]; + cekBlock[3] = (byte)~in[inOff + 2]; + + System.arraycopy(in, inOff, cekBlock, 4, inLen); + + for (int i = inLen + 4; i < cekBlock.length; i++) + { + cekBlock[i] = (byte)rand.nextInt(); + } + + for (int i = 0; i < cekBlock.length; i += blockSize) + { + engine.processBlock(cekBlock, i, cekBlock, i); + } + + for (int i = 0; i < cekBlock.length; i += blockSize) + { + engine.processBlock(cekBlock, i, cekBlock, i); + } + + return cekBlock; + } + + public byte[] unwrap( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (forWrapping) + { + throw new IllegalStateException("not set for unwrapping"); + } + + int blockSize = engine.getBlockSize(); + + if (inLen < 2 * blockSize) + { + throw new InvalidCipherTextException("input too short"); + } + + byte[] cekBlock = new byte[inLen]; + byte[] iv = new byte[blockSize]; + + System.arraycopy(in, inOff, cekBlock, 0, inLen); + System.arraycopy(in, inOff, iv, 0, iv.length); + + engine.init(false, new ParametersWithIV(param.getParameters(), iv)); + + for (int i = blockSize; i < cekBlock.length; i += blockSize) + { + engine.processBlock(cekBlock, i, cekBlock, i); + } + + System.arraycopy(cekBlock, cekBlock.length - iv.length, iv, 0, iv.length); + + engine.init(false, new ParametersWithIV(param.getParameters(), iv)); + + engine.processBlock(cekBlock, 0, cekBlock, 0); + + engine.init(false, param); + + for (int i = 0; i < cekBlock.length; i += blockSize) + { + engine.processBlock(cekBlock, i, cekBlock, i); + } + + if ((cekBlock[0] & 0xff) > cekBlock.length - 4) + { + throw new InvalidCipherTextException("wrapped key corrupted"); + } + + byte[] key = new byte[cekBlock[0] & 0xff]; + + System.arraycopy(cekBlock, 4, key, 0, cekBlock[0]); + + // Note: Using constant time comparison + int nonEqual = 0; + for (int i = 0; i != 3; i++) + { + byte check = (byte)~cekBlock[1 + i]; + nonEqual |= (check ^ key[i]); + } + if (nonEqual != 0) + { + throw new InvalidCipherTextException("wrapped key fails checksum"); + } + + return key; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/RFC3394WrapEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/RFC3394WrapEngine.java new file mode 100644 index 000000000..3a265f20e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/RFC3394WrapEngine.java @@ -0,0 +1,177 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; +import com.google.bitcoin.bouncycastle.crypto.Wrapper; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.util.Arrays; + +/** + * an implementation of the AES Key Wrapper from the NIST Key Wrap + * Specification as described in RFC 3394. + *

+ * For further details see: http://www.ietf.org/rfc/rfc3394.txt + * and http://csrc.nist.gov/encryption/kms/key-wrap.pdf. + */ +public class RFC3394WrapEngine + implements Wrapper +{ + private BlockCipher engine; + private KeyParameter param; + private boolean forWrapping; + + private byte[] iv = { + (byte)0xa6, (byte)0xa6, (byte)0xa6, (byte)0xa6, + (byte)0xa6, (byte)0xa6, (byte)0xa6, (byte)0xa6 }; + + public RFC3394WrapEngine(BlockCipher engine) + { + this.engine = engine; + } + + public void init( + boolean forWrapping, + CipherParameters param) + { + this.forWrapping = forWrapping; + + if (param instanceof ParametersWithRandom) + { + param = ((ParametersWithRandom) param).getParameters(); + } + + if (param instanceof KeyParameter) + { + this.param = (KeyParameter)param; + } + else if (param instanceof ParametersWithIV) + { + this.iv = ((ParametersWithIV)param).getIV(); + this.param = (KeyParameter)((ParametersWithIV) param).getParameters(); + if (this.iv.length != 8) + { + throw new IllegalArgumentException("IV not equal to 8"); + } + } + } + + public String getAlgorithmName() + { + return engine.getAlgorithmName(); + } + + public byte[] wrap( + byte[] in, + int inOff, + int inLen) + { + if (!forWrapping) + { + throw new IllegalStateException("not set for wrapping"); + } + + int n = inLen / 8; + + if ((n * 8) != inLen) + { + throw new DataLengthException("wrap data must be a multiple of 8 bytes"); + } + + byte[] block = new byte[inLen + iv.length]; + byte[] buf = new byte[8 + iv.length]; + + System.arraycopy(iv, 0, block, 0, iv.length); + System.arraycopy(in, 0, block, iv.length, inLen); + + engine.init(true, param); + + for (int j = 0; j != 6; j++) + { + for (int i = 1; i <= n; i++) + { + System.arraycopy(block, 0, buf, 0, iv.length); + System.arraycopy(block, 8 * i, buf, iv.length, 8); + engine.processBlock(buf, 0, buf, 0); + + int t = n * j + i; + for (int k = 1; t != 0; k++) + { + byte v = (byte)t; + + buf[iv.length - k] ^= v; + + t >>>= 8; + } + + System.arraycopy(buf, 0, block, 0, 8); + System.arraycopy(buf, 8, block, 8 * i, 8); + } + } + + return block; + } + + public byte[] unwrap( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (forWrapping) + { + throw new IllegalStateException("not set for unwrapping"); + } + + int n = inLen / 8; + + if ((n * 8) != inLen) + { + throw new InvalidCipherTextException("unwrap data must be a multiple of 8 bytes"); + } + + byte[] block = new byte[inLen - iv.length]; + byte[] a = new byte[iv.length]; + byte[] buf = new byte[8 + iv.length]; + + System.arraycopy(in, 0, a, 0, iv.length); + System.arraycopy(in, iv.length, block, 0, inLen - iv.length); + + engine.init(false, param); + + n = n - 1; + + for (int j = 5; j >= 0; j--) + { + for (int i = n; i >= 1; i--) + { + System.arraycopy(a, 0, buf, 0, iv.length); + System.arraycopy(block, 8 * (i - 1), buf, iv.length, 8); + + int t = n * j + i; + for (int k = 1; t != 0; k++) + { + byte v = (byte)t; + + buf[iv.length - k] ^= v; + + t >>>= 8; + } + + engine.processBlock(buf, 0, buf, 0); + System.arraycopy(buf, 0, a, 0, 8); + System.arraycopy(buf, 8, block, 8 * (i - 1), 8); + } + } + + if (!Arrays.constantTimeAreEqual(a, iv)) + { + throw new InvalidCipherTextException("checksum failed"); + } + + return block; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/RSABlindedEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/RSABlindedEngine.java new file mode 100644 index 000000000..efc24c3d6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/RSABlindedEngine.java @@ -0,0 +1,126 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.crypto.params.RSAKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; +import com.google.bitcoin.bouncycastle.util.BigIntegers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * this does your basic RSA algorithm with blinding + */ +public class RSABlindedEngine + implements AsymmetricBlockCipher +{ + private static BigInteger ONE = BigInteger.valueOf(1); + + private RSACoreEngine core = new RSACoreEngine(); + private RSAKeyParameters key; + private SecureRandom random; + + /** + * initialise the RSA engine. + * + * @param forEncryption true if we are encrypting, false otherwise. + * @param param the necessary RSA key parameters. + */ + public void init( + boolean forEncryption, + CipherParameters param) + { + core.init(forEncryption, param); + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + key = (RSAKeyParameters)rParam.getParameters(); + random = rParam.getRandom(); + } + else + { + key = (RSAKeyParameters)param; + random = new SecureRandom(); + } + } + + /** + * Return the maximum size for an input block to this engine. + * For RSA this is always one byte less than the key size on + * encryption, and the same length as the key size on decryption. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize() + { + return core.getInputBlockSize(); + } + + /** + * Return the maximum size for an output block to this engine. + * For RSA this is always one byte less than the key size on + * decryption, and the same length as the key size on encryption. + * + * @return maximum size for an output block. + */ + public int getOutputBlockSize() + { + return core.getOutputBlockSize(); + } + + /** + * Process a single block using the basic RSA algorithm. + * + * @param in the input array. + * @param inOff the offset into the input buffer where the data starts. + * @param inLen the length of the data to be processed. + * @return the result of the RSA process. + * @exception DataLengthException the input block is too large. + */ + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + { + if (key == null) + { + throw new IllegalStateException("RSA engine not initialised"); + } + + BigInteger input = core.convertInput(in, inOff, inLen); + + BigInteger result; + if (key instanceof RSAPrivateCrtKeyParameters) + { + RSAPrivateCrtKeyParameters k = (RSAPrivateCrtKeyParameters)key; + + BigInteger e = k.getPublicExponent(); + if (e != null) // can't do blinding without a public exponent + { + BigInteger m = k.getModulus(); + BigInteger r = BigIntegers.createRandomInRange(ONE, m.subtract(ONE), random); + + BigInteger blindedInput = r.modPow(e, m).multiply(input).mod(m); + BigInteger blindedResult = core.processBlock(blindedInput); + + BigInteger rInv = r.modInverse(m); + result = blindedResult.multiply(rInv).mod(m); + } + else + { + result = core.processBlock(input); + } + } + else + { + result = core.processBlock(input); + } + + return core.convertOutput(result); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/RSABlindingEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/RSABlindingEngine.java new file mode 100644 index 000000000..8c45fc71c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/RSABlindingEngine.java @@ -0,0 +1,137 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.crypto.params.RSABlindingParameters; +import com.google.bitcoin.bouncycastle.crypto.params.RSAKeyParameters; + +import java.math.BigInteger; + +/** + * This does your basic RSA Chaum's blinding and unblinding as outlined in + * "Handbook of Applied Cryptography", page 475. You need to use this if you are + * trying to get another party to generate signatures without them being aware + * of the message they are signing. + */ +public class RSABlindingEngine + implements AsymmetricBlockCipher +{ + private RSACoreEngine core = new RSACoreEngine(); + + private RSAKeyParameters key; + private BigInteger blindingFactor; + + private boolean forEncryption; + + /** + * Initialise the blinding engine. + * + * @param forEncryption true if we are encrypting (blinding), false otherwise. + * @param param the necessary RSA key parameters. + */ + public void init( + boolean forEncryption, + CipherParameters param) + { + RSABlindingParameters p; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + p = (RSABlindingParameters)rParam.getParameters(); + } + else + { + p = (RSABlindingParameters)param; + } + + core.init(forEncryption, p.getPublicKey()); + + this.forEncryption = forEncryption; + this.key = p.getPublicKey(); + this.blindingFactor = p.getBlindingFactor(); + } + + /** + * Return the maximum size for an input block to this engine. + * For RSA this is always one byte less than the key size on + * encryption, and the same length as the key size on decryption. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize() + { + return core.getInputBlockSize(); + } + + /** + * Return the maximum size for an output block to this engine. + * For RSA this is always one byte less than the key size on + * decryption, and the same length as the key size on encryption. + * + * @return maximum size for an output block. + */ + public int getOutputBlockSize() + { + return core.getOutputBlockSize(); + } + + /** + * Process a single block using the RSA blinding algorithm. + * + * @param in the input array. + * @param inOff the offset into the input buffer where the data starts. + * @param inLen the length of the data to be processed. + * @return the result of the RSA process. + * @throws DataLengthException the input block is too large. + */ + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + { + BigInteger msg = core.convertInput(in, inOff, inLen); + + if (forEncryption) + { + msg = blindMessage(msg); + } + else + { + msg = unblindMessage(msg); + } + + return core.convertOutput(msg); + } + + /* + * Blind message with the blind factor. + */ + private BigInteger blindMessage( + BigInteger msg) + { + BigInteger blindMsg = blindingFactor; + blindMsg = msg.multiply(blindMsg.modPow(key.getExponent(), key.getModulus())); + blindMsg = blindMsg.mod(key.getModulus()); + + return blindMsg; + } + + /* + * Unblind the message blinded with the blind factor. + */ + private BigInteger unblindMessage( + BigInteger blindedMsg) + { + BigInteger m = key.getModulus(); + BigInteger msg = blindedMsg; + BigInteger blindFactorInverse = blindingFactor.modInverse(m); + msg = msg.multiply(blindFactorInverse); + msg = msg.mod(m); + + return msg; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/RSACoreEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/RSACoreEngine.java new file mode 100644 index 000000000..6ebc67229 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/RSACoreEngine.java @@ -0,0 +1,203 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.crypto.params.RSAKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; + +import java.math.BigInteger; + +/** + * this does your basic RSA algorithm. + */ +class RSACoreEngine +{ + private RSAKeyParameters key; + private boolean forEncryption; + + /** + * initialise the RSA engine. + * + * @param forEncryption true if we are encrypting, false otherwise. + * @param param the necessary RSA key parameters. + */ + public void init( + boolean forEncryption, + CipherParameters param) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + key = (RSAKeyParameters)rParam.getParameters(); + } + else + { + key = (RSAKeyParameters)param; + } + + this.forEncryption = forEncryption; + } + + /** + * Return the maximum size for an input block to this engine. + * For RSA this is always one byte less than the key size on + * encryption, and the same length as the key size on decryption. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize() + { + int bitSize = key.getModulus().bitLength(); + + if (forEncryption) + { + return (bitSize + 7) / 8 - 1; + } + else + { + return (bitSize + 7) / 8; + } + } + + /** + * Return the maximum size for an output block to this engine. + * For RSA this is always one byte less than the key size on + * decryption, and the same length as the key size on encryption. + * + * @return maximum size for an output block. + */ + public int getOutputBlockSize() + { + int bitSize = key.getModulus().bitLength(); + + if (forEncryption) + { + return (bitSize + 7) / 8; + } + else + { + return (bitSize + 7) / 8 - 1; + } + } + + public BigInteger convertInput( + byte[] in, + int inOff, + int inLen) + { + if (inLen > (getInputBlockSize() + 1)) + { + throw new DataLengthException("input too large for RSA cipher."); + } + else if (inLen == (getInputBlockSize() + 1) && !forEncryption) + { + throw new DataLengthException("input too large for RSA cipher."); + } + + byte[] block; + + if (inOff != 0 || inLen != in.length) + { + block = new byte[inLen]; + + System.arraycopy(in, inOff, block, 0, inLen); + } + else + { + block = in; + } + + BigInteger res = new BigInteger(1, block); + if (res.compareTo(key.getModulus()) >= 0) + { + throw new DataLengthException("input too large for RSA cipher."); + } + + return res; + } + + public byte[] convertOutput( + BigInteger result) + { + byte[] output = result.toByteArray(); + + if (forEncryption) + { + if (output[0] == 0 && output.length > getOutputBlockSize()) // have ended up with an extra zero byte, copy down. + { + byte[] tmp = new byte[output.length - 1]; + + System.arraycopy(output, 1, tmp, 0, tmp.length); + + return tmp; + } + + if (output.length < getOutputBlockSize()) // have ended up with less bytes than normal, lengthen + { + byte[] tmp = new byte[getOutputBlockSize()]; + + System.arraycopy(output, 0, tmp, tmp.length - output.length, output.length); + + return tmp; + } + } + else + { + if (output[0] == 0) // have ended up with an extra zero byte, copy down. + { + byte[] tmp = new byte[output.length - 1]; + + System.arraycopy(output, 1, tmp, 0, tmp.length); + + return tmp; + } + } + + return output; + } + + public BigInteger processBlock(BigInteger input) + { + if (key instanceof RSAPrivateCrtKeyParameters) + { + // + // we have the extra factors, use the Chinese Remainder Theorem - the author + // wishes to express his thanks to Dirk Bonekaemper at rtsffm.com for + // advice regarding the expression of this. + // + RSAPrivateCrtKeyParameters crtKey = (RSAPrivateCrtKeyParameters)key; + + BigInteger p = crtKey.getP(); + BigInteger q = crtKey.getQ(); + BigInteger dP = crtKey.getDP(); + BigInteger dQ = crtKey.getDQ(); + BigInteger qInv = crtKey.getQInv(); + + BigInteger mP, mQ, h, m; + + // mP = ((input mod p) ^ dP)) mod p + mP = (input.remainder(p)).modPow(dP, p); + + // mQ = ((input mod q) ^ dQ)) mod q + mQ = (input.remainder(q)).modPow(dQ, q); + + // h = qInv * (mP - mQ) mod p + h = mP.subtract(mQ); + h = h.multiply(qInv); + h = h.mod(p); // mod (in Java) returns the positive residual + + // m = h * q + mQ + m = h.multiply(q); + m = m.add(mQ); + + return m; + } + else + { + return input.modPow( + key.getExponent(), key.getModulus()); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/RSAEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/RSAEngine.java new file mode 100644 index 000000000..5bd33eae0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/RSAEngine.java @@ -0,0 +1,78 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; + +/** + * this does your basic RSA algorithm. + */ +public class RSAEngine + implements AsymmetricBlockCipher +{ + private RSACoreEngine core; + + /** + * initialise the RSA engine. + * + * @param forEncryption true if we are encrypting, false otherwise. + * @param param the necessary RSA key parameters. + */ + public void init( + boolean forEncryption, + CipherParameters param) + { + if (core == null) + { + core = new RSACoreEngine(); + } + + core.init(forEncryption, param); + } + + /** + * Return the maximum size for an input block to this engine. + * For RSA this is always one byte less than the key size on + * encryption, and the same length as the key size on decryption. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize() + { + return core.getInputBlockSize(); + } + + /** + * Return the maximum size for an output block to this engine. + * For RSA this is always one byte less than the key size on + * decryption, and the same length as the key size on encryption. + * + * @return maximum size for an output block. + */ + public int getOutputBlockSize() + { + return core.getOutputBlockSize(); + } + + /** + * Process a single block using the basic RSA algorithm. + * + * @param in the input array. + * @param inOff the offset into the input buffer where the data starts. + * @param inLen the length of the data to be processed. + * @return the result of the RSA process. + * @exception DataLengthException the input block is too large. + */ + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + { + if (core == null) + { + throw new IllegalStateException("RSA engine not initialised"); + } + + return core.convertOutput(core.processBlock(core.convertInput(in, inOff, inLen))); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/RijndaelEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/RijndaelEngine.java new file mode 100644 index 000000000..089b71c3b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/RijndaelEngine.java @@ -0,0 +1,724 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * an implementation of Rijndael, based on the documentation and reference implementation + * by Paulo Barreto, Vincent Rijmen, for v2.0 August '99. + *

+ * Note: this implementation is based on information prior to final NIST publication. + */ +public class RijndaelEngine + implements BlockCipher +{ + private static final int MAXROUNDS = 14; + + private static final int MAXKC = (256/4); + + private static final byte[] logtable = { + (byte)0, (byte)0, (byte)25, (byte)1, (byte)50, (byte)2, (byte)26, (byte)198, + (byte)75, (byte)199, (byte)27, (byte)104, (byte)51, (byte)238, (byte)223, (byte)3, + (byte)100, (byte)4, (byte)224, (byte)14, (byte)52, (byte)141, (byte)129, (byte)239, + (byte)76, (byte)113, (byte)8, (byte)200, (byte)248, (byte)105, (byte)28, (byte)193, + (byte)125, (byte)194, (byte)29, (byte)181, (byte)249, (byte)185, (byte)39, (byte)106, + (byte)77, (byte)228, (byte)166, (byte)114, (byte)154, (byte)201, (byte)9, (byte)120, + (byte)101, (byte)47, (byte)138, (byte)5, (byte)33, (byte)15, (byte)225, (byte)36, + (byte)18, (byte)240, (byte)130, (byte)69, (byte)53, (byte)147, (byte)218, (byte)142, + (byte)150, (byte)143, (byte)219, (byte)189, (byte)54, (byte)208, (byte)206, (byte)148, + (byte)19, (byte)92, (byte)210, (byte)241, (byte)64, (byte)70, (byte)131, (byte)56, + (byte)102, (byte)221, (byte)253, (byte)48, (byte)191, (byte)6, (byte)139, (byte)98, + (byte)179, (byte)37, (byte)226, (byte)152, (byte)34, (byte)136, (byte)145, (byte)16, + (byte)126, (byte)110, (byte)72, (byte)195, (byte)163, (byte)182, (byte)30, (byte)66, + (byte)58, (byte)107, (byte)40, (byte)84, (byte)250, (byte)133, (byte)61, (byte)186, + (byte)43, (byte)121, (byte)10, (byte)21, (byte)155, (byte)159, (byte)94, (byte)202, + (byte)78, (byte)212, (byte)172, (byte)229, (byte)243, (byte)115, (byte)167, (byte)87, + (byte)175, (byte)88, (byte)168, (byte)80, (byte)244, (byte)234, (byte)214, (byte)116, + (byte)79, (byte)174, (byte)233, (byte)213, (byte)231, (byte)230, (byte)173, (byte)232, + (byte)44, (byte)215, (byte)117, (byte)122, (byte)235, (byte)22, (byte)11, (byte)245, + (byte)89, (byte)203, (byte)95, (byte)176, (byte)156, (byte)169, (byte)81, (byte)160, + (byte)127, (byte)12, (byte)246, (byte)111, (byte)23, (byte)196, (byte)73, (byte)236, + (byte)216, (byte)67, (byte)31, (byte)45, (byte)164, (byte)118, (byte)123, (byte)183, + (byte)204, (byte)187, (byte)62, (byte)90, (byte)251, (byte)96, (byte)177, (byte)134, + (byte)59, (byte)82, (byte)161, (byte)108, (byte)170, (byte)85, (byte)41, (byte)157, + (byte)151, (byte)178, (byte)135, (byte)144, (byte)97, (byte)190, (byte)220, (byte)252, + (byte)188, (byte)149, (byte)207, (byte)205, (byte)55, (byte)63, (byte)91, (byte)209, + (byte)83, (byte)57, (byte)132, (byte)60, (byte)65, (byte)162, (byte)109, (byte)71, + (byte)20, (byte)42, (byte)158, (byte)93, (byte)86, (byte)242, (byte)211, (byte)171, + (byte)68, (byte)17, (byte)146, (byte)217, (byte)35, (byte)32, (byte)46, (byte)137, + (byte)180, (byte)124, (byte)184, (byte)38, (byte)119, (byte)153, (byte)227, (byte)165, + (byte)103, (byte)74, (byte)237, (byte)222, (byte)197, (byte)49, (byte)254, (byte)24, + (byte)13, (byte)99, (byte)140, (byte)128, (byte)192, (byte)247, (byte)112, (byte)7 + }; + + private static final byte[] aLogtable = { + (byte)0, (byte)3, (byte)5, (byte)15, (byte)17, (byte)51, (byte)85, (byte)255, (byte)26, (byte)46, (byte)114, (byte)150, (byte)161, (byte)248, (byte)19, (byte)53, + (byte)95, (byte)225, (byte)56, (byte)72, (byte)216, (byte)115, (byte)149, (byte)164, (byte)247, (byte)2, (byte)6, (byte)10, (byte)30, (byte)34, (byte)102, (byte)170, + (byte)229, (byte)52, (byte)92, (byte)228, (byte)55, (byte)89, (byte)235, (byte)38, (byte)106, (byte)190, (byte)217, (byte)112, (byte)144, (byte)171, (byte)230, (byte)49, + (byte)83, (byte)245, (byte)4, (byte)12, (byte)20, (byte)60, (byte)68, (byte)204, (byte)79, (byte)209, (byte)104, (byte)184, (byte)211, (byte)110, (byte)178, (byte)205, + (byte)76, (byte)212, (byte)103, (byte)169, (byte)224, (byte)59, (byte)77, (byte)215, (byte)98, (byte)166, (byte)241, (byte)8, (byte)24, (byte)40, (byte)120, (byte)136, + (byte)131, (byte)158, (byte)185, (byte)208, (byte)107, (byte)189, (byte)220, (byte)127, (byte)129, (byte)152, (byte)179, (byte)206, (byte)73, (byte)219, (byte)118, (byte)154, + (byte)181, (byte)196, (byte)87, (byte)249, (byte)16, (byte)48, (byte)80, (byte)240, (byte)11, (byte)29, (byte)39, (byte)105, (byte)187, (byte)214, (byte)97, (byte)163, + (byte)254, (byte)25, (byte)43, (byte)125, (byte)135, (byte)146, (byte)173, (byte)236, (byte)47, (byte)113, (byte)147, (byte)174, (byte)233, (byte)32, (byte)96, (byte)160, + (byte)251, (byte)22, (byte)58, (byte)78, (byte)210, (byte)109, (byte)183, (byte)194, (byte)93, (byte)231, (byte)50, (byte)86, (byte)250, (byte)21, (byte)63, (byte)65, + (byte)195, (byte)94, (byte)226, (byte)61, (byte)71, (byte)201, (byte)64, (byte)192, (byte)91, (byte)237, (byte)44, (byte)116, (byte)156, (byte)191, (byte)218, (byte)117, + (byte)159, (byte)186, (byte)213, (byte)100, (byte)172, (byte)239, (byte)42, (byte)126, (byte)130, (byte)157, (byte)188, (byte)223, (byte)122, (byte)142, (byte)137, (byte)128, + (byte)155, (byte)182, (byte)193, (byte)88, (byte)232, (byte)35, (byte)101, (byte)175, (byte)234, (byte)37, (byte)111, (byte)177, (byte)200, (byte)67, (byte)197, (byte)84, + (byte)252, (byte)31, (byte)33, (byte)99, (byte)165, (byte)244, (byte)7, (byte)9, (byte)27, (byte)45, (byte)119, (byte)153, (byte)176, (byte)203, (byte)70, (byte)202, + (byte)69, (byte)207, (byte)74, (byte)222, (byte)121, (byte)139, (byte)134, (byte)145, (byte)168, (byte)227, (byte)62, (byte)66, (byte)198, (byte)81, (byte)243, (byte)14, + (byte)18, (byte)54, (byte)90, (byte)238, (byte)41, (byte)123, (byte)141, (byte)140, (byte)143, (byte)138, (byte)133, (byte)148, (byte)167, (byte)242, (byte)13, (byte)23, + (byte)57, (byte)75, (byte)221, (byte)124, (byte)132, (byte)151, (byte)162, (byte)253, (byte)28, (byte)36, (byte)108, (byte)180, (byte)199, (byte)82, (byte)246, (byte)1, + (byte)3, (byte)5, (byte)15, (byte)17, (byte)51, (byte)85, (byte)255, (byte)26, (byte)46, (byte)114, (byte)150, (byte)161, (byte)248, (byte)19, (byte)53, + (byte)95, (byte)225, (byte)56, (byte)72, (byte)216, (byte)115, (byte)149, (byte)164, (byte)247, (byte)2, (byte)6, (byte)10, (byte)30, (byte)34, (byte)102, (byte)170, + (byte)229, (byte)52, (byte)92, (byte)228, (byte)55, (byte)89, (byte)235, (byte)38, (byte)106, (byte)190, (byte)217, (byte)112, (byte)144, (byte)171, (byte)230, (byte)49, + (byte)83, (byte)245, (byte)4, (byte)12, (byte)20, (byte)60, (byte)68, (byte)204, (byte)79, (byte)209, (byte)104, (byte)184, (byte)211, (byte)110, (byte)178, (byte)205, + (byte)76, (byte)212, (byte)103, (byte)169, (byte)224, (byte)59, (byte)77, (byte)215, (byte)98, (byte)166, (byte)241, (byte)8, (byte)24, (byte)40, (byte)120, (byte)136, + (byte)131, (byte)158, (byte)185, (byte)208, (byte)107, (byte)189, (byte)220, (byte)127, (byte)129, (byte)152, (byte)179, (byte)206, (byte)73, (byte)219, (byte)118, (byte)154, + (byte)181, (byte)196, (byte)87, (byte)249, (byte)16, (byte)48, (byte)80, (byte)240, (byte)11, (byte)29, (byte)39, (byte)105, (byte)187, (byte)214, (byte)97, (byte)163, + (byte)254, (byte)25, (byte)43, (byte)125, (byte)135, (byte)146, (byte)173, (byte)236, (byte)47, (byte)113, (byte)147, (byte)174, (byte)233, (byte)32, (byte)96, (byte)160, + (byte)251, (byte)22, (byte)58, (byte)78, (byte)210, (byte)109, (byte)183, (byte)194, (byte)93, (byte)231, (byte)50, (byte)86, (byte)250, (byte)21, (byte)63, (byte)65, + (byte)195, (byte)94, (byte)226, (byte)61, (byte)71, (byte)201, (byte)64, (byte)192, (byte)91, (byte)237, (byte)44, (byte)116, (byte)156, (byte)191, (byte)218, (byte)117, + (byte)159, (byte)186, (byte)213, (byte)100, (byte)172, (byte)239, (byte)42, (byte)126, (byte)130, (byte)157, (byte)188, (byte)223, (byte)122, (byte)142, (byte)137, (byte)128, + (byte)155, (byte)182, (byte)193, (byte)88, (byte)232, (byte)35, (byte)101, (byte)175, (byte)234, (byte)37, (byte)111, (byte)177, (byte)200, (byte)67, (byte)197, (byte)84, + (byte)252, (byte)31, (byte)33, (byte)99, (byte)165, (byte)244, (byte)7, (byte)9, (byte)27, (byte)45, (byte)119, (byte)153, (byte)176, (byte)203, (byte)70, (byte)202, + (byte)69, (byte)207, (byte)74, (byte)222, (byte)121, (byte)139, (byte)134, (byte)145, (byte)168, (byte)227, (byte)62, (byte)66, (byte)198, (byte)81, (byte)243, (byte)14, + (byte)18, (byte)54, (byte)90, (byte)238, (byte)41, (byte)123, (byte)141, (byte)140, (byte)143, (byte)138, (byte)133, (byte)148, (byte)167, (byte)242, (byte)13, (byte)23, + (byte)57, (byte)75, (byte)221, (byte)124, (byte)132, (byte)151, (byte)162, (byte)253, (byte)28, (byte)36, (byte)108, (byte)180, (byte)199, (byte)82, (byte)246, (byte)1, + }; + + private static final byte[] S = { + (byte)99, (byte)124, (byte)119, (byte)123, (byte)242, (byte)107, (byte)111, (byte)197, (byte)48, (byte)1, (byte)103, (byte)43, (byte)254, (byte)215, (byte)171, (byte)118, + (byte)202, (byte)130, (byte)201, (byte)125, (byte)250, (byte)89, (byte)71, (byte)240, (byte)173, (byte)212, (byte)162, (byte)175, (byte)156, (byte)164, (byte)114, (byte)192, + (byte)183, (byte)253, (byte)147, (byte)38, (byte)54, (byte)63, (byte)247, (byte)204, (byte)52, (byte)165, (byte)229, (byte)241, (byte)113, (byte)216, (byte)49, (byte)21, + (byte)4, (byte)199, (byte)35, (byte)195, (byte)24, (byte)150, (byte)5, (byte)154, (byte)7, (byte)18, (byte)128, (byte)226, (byte)235, (byte)39, (byte)178, (byte)117, + (byte)9, (byte)131, (byte)44, (byte)26, (byte)27, (byte)110, (byte)90, (byte)160, (byte)82, (byte)59, (byte)214, (byte)179, (byte)41, (byte)227, (byte)47, (byte)132, + (byte)83, (byte)209, (byte)0, (byte)237, (byte)32, (byte)252, (byte)177, (byte)91, (byte)106, (byte)203, (byte)190, (byte)57, (byte)74, (byte)76, (byte)88, (byte)207, + (byte)208, (byte)239, (byte)170, (byte)251, (byte)67, (byte)77, (byte)51, (byte)133, (byte)69, (byte)249, (byte)2, (byte)127, (byte)80, (byte)60, (byte)159, (byte)168, + (byte)81, (byte)163, (byte)64, (byte)143, (byte)146, (byte)157, (byte)56, (byte)245, (byte)188, (byte)182, (byte)218, (byte)33, (byte)16, (byte)255, (byte)243, (byte)210, + (byte)205, (byte)12, (byte)19, (byte)236, (byte)95, (byte)151, (byte)68, (byte)23, (byte)196, (byte)167, (byte)126, (byte)61, (byte)100, (byte)93, (byte)25, (byte)115, + (byte)96, (byte)129, (byte)79, (byte)220, (byte)34, (byte)42, (byte)144, (byte)136, (byte)70, (byte)238, (byte)184, (byte)20, (byte)222, (byte)94, (byte)11, (byte)219, + (byte)224, (byte)50, (byte)58, (byte)10, (byte)73, (byte)6, (byte)36, (byte)92, (byte)194, (byte)211, (byte)172, (byte)98, (byte)145, (byte)149, (byte)228, (byte)121, + (byte)231, (byte)200, (byte)55, (byte)109, (byte)141, (byte)213, (byte)78, (byte)169, (byte)108, (byte)86, (byte)244, (byte)234, (byte)101, (byte)122, (byte)174, (byte)8, + (byte)186, (byte)120, (byte)37, (byte)46, (byte)28, (byte)166, (byte)180, (byte)198, (byte)232, (byte)221, (byte)116, (byte)31, (byte)75, (byte)189, (byte)139, (byte)138, + (byte)112, (byte)62, (byte)181, (byte)102, (byte)72, (byte)3, (byte)246, (byte)14, (byte)97, (byte)53, (byte)87, (byte)185, (byte)134, (byte)193, (byte)29, (byte)158, + (byte)225, (byte)248, (byte)152, (byte)17, (byte)105, (byte)217, (byte)142, (byte)148, (byte)155, (byte)30, (byte)135, (byte)233, (byte)206, (byte)85, (byte)40, (byte)223, + (byte)140, (byte)161, (byte)137, (byte)13, (byte)191, (byte)230, (byte)66, (byte)104, (byte)65, (byte)153, (byte)45, (byte)15, (byte)176, (byte)84, (byte)187, (byte)22, + }; + + private static final byte[] Si = { + (byte)82, (byte)9, (byte)106, (byte)213, (byte)48, (byte)54, (byte)165, (byte)56, (byte)191, (byte)64, (byte)163, (byte)158, (byte)129, (byte)243, (byte)215, (byte)251, + (byte)124, (byte)227, (byte)57, (byte)130, (byte)155, (byte)47, (byte)255, (byte)135, (byte)52, (byte)142, (byte)67, (byte)68, (byte)196, (byte)222, (byte)233, (byte)203, + (byte)84, (byte)123, (byte)148, (byte)50, (byte)166, (byte)194, (byte)35, (byte)61, (byte)238, (byte)76, (byte)149, (byte)11, (byte)66, (byte)250, (byte)195, (byte)78, + (byte)8, (byte)46, (byte)161, (byte)102, (byte)40, (byte)217, (byte)36, (byte)178, (byte)118, (byte)91, (byte)162, (byte)73, (byte)109, (byte)139, (byte)209, (byte)37, + (byte)114, (byte)248, (byte)246, (byte)100, (byte)134, (byte)104, (byte)152, (byte)22, (byte)212, (byte)164, (byte)92, (byte)204, (byte)93, (byte)101, (byte)182, (byte)146, + (byte)108, (byte)112, (byte)72, (byte)80, (byte)253, (byte)237, (byte)185, (byte)218, (byte)94, (byte)21, (byte)70, (byte)87, (byte)167, (byte)141, (byte)157, (byte)132, + (byte)144, (byte)216, (byte)171, (byte)0, (byte)140, (byte)188, (byte)211, (byte)10, (byte)247, (byte)228, (byte)88, (byte)5, (byte)184, (byte)179, (byte)69, (byte)6, + (byte)208, (byte)44, (byte)30, (byte)143, (byte)202, (byte)63, (byte)15, (byte)2, (byte)193, (byte)175, (byte)189, (byte)3, (byte)1, (byte)19, (byte)138, (byte)107, + (byte)58, (byte)145, (byte)17, (byte)65, (byte)79, (byte)103, (byte)220, (byte)234, (byte)151, (byte)242, (byte)207, (byte)206, (byte)240, (byte)180, (byte)230, (byte)115, + (byte)150, (byte)172, (byte)116, (byte)34, (byte)231, (byte)173, (byte)53, (byte)133, (byte)226, (byte)249, (byte)55, (byte)232, (byte)28, (byte)117, (byte)223, (byte)110, + (byte)71, (byte)241, (byte)26, (byte)113, (byte)29, (byte)41, (byte)197, (byte)137, (byte)111, (byte)183, (byte)98, (byte)14, (byte)170, (byte)24, (byte)190, (byte)27, + (byte)252, (byte)86, (byte)62, (byte)75, (byte)198, (byte)210, (byte)121, (byte)32, (byte)154, (byte)219, (byte)192, (byte)254, (byte)120, (byte)205, (byte)90, (byte)244, + (byte)31, (byte)221, (byte)168, (byte)51, (byte)136, (byte)7, (byte)199, (byte)49, (byte)177, (byte)18, (byte)16, (byte)89, (byte)39, (byte)128, (byte)236, (byte)95, + (byte)96, (byte)81, (byte)127, (byte)169, (byte)25, (byte)181, (byte)74, (byte)13, (byte)45, (byte)229, (byte)122, (byte)159, (byte)147, (byte)201, (byte)156, (byte)239, + (byte)160, (byte)224, (byte)59, (byte)77, (byte)174, (byte)42, (byte)245, (byte)176, (byte)200, (byte)235, (byte)187, (byte)60, (byte)131, (byte)83, (byte)153, (byte)97, + (byte)23, (byte)43, (byte)4, (byte)126, (byte)186, (byte)119, (byte)214, (byte)38, (byte)225, (byte)105, (byte)20, (byte)99, (byte)85, (byte)33, (byte)12, (byte)125, + }; + + private static final int[] rcon = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 }; + + static byte[][] shifts0 = + { + { 0, 8, 16, 24 }, + { 0, 8, 16, 24 }, + { 0, 8, 16, 24 }, + { 0, 8, 16, 32 }, + { 0, 8, 24, 32 } + }; + + static byte[][] shifts1 = + { + { 0, 24, 16, 8 }, + { 0, 32, 24, 16 }, + { 0, 40, 32, 24 }, + { 0, 48, 40, 24 }, + { 0, 56, 40, 32 } + }; + + /** + * multiply two elements of GF(2^m) + * needed for MixColumn and InvMixColumn + */ + private byte mul0x2( + int b) + { + if (b != 0) + { + return aLogtable[25 + (logtable[b] & 0xff)]; + } + else + { + return 0; + } + } + + private byte mul0x3( + int b) + { + if (b != 0) + { + return aLogtable[1 + (logtable[b] & 0xff)]; + } + else + { + return 0; + } + } + + private byte mul0x9( + int b) + { + if (b >= 0) + { + return aLogtable[199 + b]; + } + else + { + return 0; + } + } + + private byte mul0xb( + int b) + { + if (b >= 0) + { + return aLogtable[104 + b]; + } + else + { + return 0; + } + } + + private byte mul0xd( + int b) + { + if (b >= 0) + { + return aLogtable[238 + b]; + } + else + { + return 0; + } + } + + private byte mul0xe( + int b) + { + if (b >= 0) + { + return aLogtable[223 + b]; + } + else + { + return 0; + } + } + + /** + * xor corresponding text input and round key input bytes + */ + private void KeyAddition( + long[] rk) + { + A0 ^= rk[0]; + A1 ^= rk[1]; + A2 ^= rk[2]; + A3 ^= rk[3]; + } + + private long shift( + long r, + int shift) + { + return (((r >>> shift) | (r << (BC - shift)))) & BC_MASK; + } + + /** + * Row 0 remains unchanged + * The other three rows are shifted a variable amount + */ + private void ShiftRow( + byte[] shiftsSC) + { + A1 = shift(A1, shiftsSC[1]); + A2 = shift(A2, shiftsSC[2]); + A3 = shift(A3, shiftsSC[3]); + } + + private long applyS( + long r, + byte[] box) + { + long res = 0; + + for (int j = 0; j < BC; j += 8) + { + res |= (long)(box[(int)((r >> j) & 0xff)] & 0xff) << j; + } + + return res; + } + + /** + * Replace every byte of the input by the byte at that place + * in the nonlinear S-box + */ + private void Substitution( + byte[] box) + { + A0 = applyS(A0, box); + A1 = applyS(A1, box); + A2 = applyS(A2, box); + A3 = applyS(A3, box); + } + + /** + * Mix the bytes of every column in a linear way + */ + private void MixColumn() + { + long r0, r1, r2, r3; + + r0 = r1 = r2 = r3 = 0; + + for (int j = 0; j < BC; j += 8) + { + int a0 = (int)((A0 >> j) & 0xff); + int a1 = (int)((A1 >> j) & 0xff); + int a2 = (int)((A2 >> j) & 0xff); + int a3 = (int)((A3 >> j) & 0xff); + + r0 |= (long)((mul0x2(a0) ^ mul0x3(a1) ^ a2 ^ a3) & 0xff) << j; + + r1 |= (long)((mul0x2(a1) ^ mul0x3(a2) ^ a3 ^ a0) & 0xff) << j; + + r2 |= (long)((mul0x2(a2) ^ mul0x3(a3) ^ a0 ^ a1) & 0xff) << j; + + r3 |= (long)((mul0x2(a3) ^ mul0x3(a0) ^ a1 ^ a2) & 0xff) << j; + } + + A0 = r0; + A1 = r1; + A2 = r2; + A3 = r3; + } + + /** + * Mix the bytes of every column in a linear way + * This is the opposite operation of Mixcolumn + */ + private void InvMixColumn() + { + long r0, r1, r2, r3; + + r0 = r1 = r2 = r3 = 0; + for (int j = 0; j < BC; j += 8) + { + int a0 = (int)((A0 >> j) & 0xff); + int a1 = (int)((A1 >> j) & 0xff); + int a2 = (int)((A2 >> j) & 0xff); + int a3 = (int)((A3 >> j) & 0xff); + + // + // pre-lookup the log table + // + a0 = (a0 != 0) ? (logtable[a0 & 0xff] & 0xff) : -1; + a1 = (a1 != 0) ? (logtable[a1 & 0xff] & 0xff) : -1; + a2 = (a2 != 0) ? (logtable[a2 & 0xff] & 0xff) : -1; + a3 = (a3 != 0) ? (logtable[a3 & 0xff] & 0xff) : -1; + + r0 |= (long)((mul0xe(a0) ^ mul0xb(a1) ^ mul0xd(a2) ^ mul0x9(a3)) & 0xff) << j; + + r1 |= (long)((mul0xe(a1) ^ mul0xb(a2) ^ mul0xd(a3) ^ mul0x9(a0)) & 0xff) << j; + + r2 |= (long)((mul0xe(a2) ^ mul0xb(a3) ^ mul0xd(a0) ^ mul0x9(a1)) & 0xff) << j; + + r3 |= (long)((mul0xe(a3) ^ mul0xb(a0) ^ mul0xd(a1) ^ mul0x9(a2)) & 0xff) << j; + } + + A0 = r0; + A1 = r1; + A2 = r2; + A3 = r3; + } + + /** + * Calculate the necessary round keys + * The number of calculations depends on keyBits and blockBits + */ + private long[][] generateWorkingKey( + byte[] key) + { + int KC; + int t, rconpointer = 0; + int keyBits = key.length * 8; + byte[][] tk = new byte[4][MAXKC]; + long[][] W = new long[MAXROUNDS+1][4]; + + switch (keyBits) + { + case 128: + KC = 4; + break; + case 160: + KC = 5; + break; + case 192: + KC = 6; + break; + case 224: + KC = 7; + break; + case 256: + KC = 8; + break; + default : + throw new IllegalArgumentException("Key length not 128/160/192/224/256 bits."); + } + + if (keyBits >= blockBits) + { + ROUNDS = KC + 6; + } + else + { + ROUNDS = (BC / 8) + 6; + } + + // + // copy the key into the processing area + // + int index = 0; + + for (int i = 0; i < key.length; i++) + { + tk[i % 4][i / 4] = key[index++]; + } + + t = 0; + + // + // copy values into round key array + // + for (int j = 0; (j < KC) && (t < (ROUNDS+1)*(BC / 8)); j++, t++) + { + for (int i = 0; i < 4; i++) + { + W[t / (BC / 8)][i] |= (long)(tk[i][j] & 0xff) << ((t * 8) % BC); + } + } + + // + // while not enough round key material calculated + // calculate new values + // + while (t < (ROUNDS+1)*(BC/8)) + { + for (int i = 0; i < 4; i++) + { + tk[i][0] ^= S[tk[(i+1)%4][KC-1] & 0xff]; + } + tk[0][0] ^= rcon[rconpointer++]; + + if (KC <= 6) + { + for (int j = 1; j < KC; j++) + { + for (int i = 0; i < 4; i++) + { + tk[i][j] ^= tk[i][j-1]; + } + } + } + else + { + for (int j = 1; j < 4; j++) + { + for (int i = 0; i < 4; i++) + { + tk[i][j] ^= tk[i][j-1]; + } + } + for (int i = 0; i < 4; i++) + { + tk[i][4] ^= S[tk[i][3] & 0xff]; + } + for (int j = 5; j < KC; j++) + { + for (int i = 0; i < 4; i++) + { + tk[i][j] ^= tk[i][j-1]; + } + } + } + + // + // copy values into round key array + // + for (int j = 0; (j < KC) && (t < (ROUNDS+1)*(BC/8)); j++, t++) + { + for (int i = 0; i < 4; i++) + { + W[t / (BC/8)][i] |= (long)(tk[i][j] & 0xff) << ((t * 8) % (BC)); + } + } + } + + return W; + } + + private int BC; + private long BC_MASK; + private int ROUNDS; + private int blockBits; + private long[][] workingKey; + private long A0, A1, A2, A3; + private boolean forEncryption; + private byte[] shifts0SC; + private byte[] shifts1SC; + + /** + * default constructor - 128 bit block size. + */ + public RijndaelEngine() + { + this(128); + } + + /** + * basic constructor - set the cipher up for a given blocksize + * + * @param blockBits the blocksize in bits, must be 128, 192, or 256. + */ + public RijndaelEngine( + int blockBits) + { + switch (blockBits) + { + case 128: + BC = 32; + BC_MASK = 0xffffffffL; + shifts0SC = shifts0[0]; + shifts1SC = shifts1[0]; + break; + case 160: + BC = 40; + BC_MASK = 0xffffffffffL; + shifts0SC = shifts0[1]; + shifts1SC = shifts1[1]; + break; + case 192: + BC = 48; + BC_MASK = 0xffffffffffffL; + shifts0SC = shifts0[2]; + shifts1SC = shifts1[2]; + break; + case 224: + BC = 56; + BC_MASK = 0xffffffffffffffL; + shifts0SC = shifts0[3]; + shifts1SC = shifts1[3]; + break; + case 256: + BC = 64; + BC_MASK = 0xffffffffffffffffL; + shifts0SC = shifts0[4]; + shifts1SC = shifts1[4]; + break; + default: + throw new IllegalArgumentException("unknown blocksize to Rijndael"); + } + + this.blockBits = blockBits; + } + + /** + * initialise a Rijndael cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + workingKey = generateWorkingKey(((KeyParameter)params).getKey()); + this.forEncryption = forEncryption; + return; + } + + throw new IllegalArgumentException("invalid parameter passed to Rijndael init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "Rijndael"; + } + + public int getBlockSize() + { + return BC / 2; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("Rijndael engine not initialised"); + } + + if ((inOff + (BC / 2)) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + (BC / 2)) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (forEncryption) + { + unpackBlock(in, inOff); + encryptBlock(workingKey); + packBlock(out, outOff); + } + else + { + unpackBlock(in, inOff); + decryptBlock(workingKey); + packBlock(out, outOff); + } + + return BC / 2; + } + + public void reset() + { + } + + private void unpackBlock( + byte[] bytes, + int off) + { + int index = off; + + A0 = (bytes[index++] & 0xff); + A1 = (bytes[index++] & 0xff); + A2 = (bytes[index++] & 0xff); + A3 = (bytes[index++] & 0xff); + + for (int j = 8; j != BC; j += 8) + { + A0 |= (long)(bytes[index++] & 0xff) << j; + A1 |= (long)(bytes[index++] & 0xff) << j; + A2 |= (long)(bytes[index++] & 0xff) << j; + A3 |= (long)(bytes[index++] & 0xff) << j; + } + } + + private void packBlock( + byte[] bytes, + int off) + { + int index = off; + + for (int j = 0; j != BC; j += 8) + { + bytes[index++] = (byte)(A0 >> j); + bytes[index++] = (byte)(A1 >> j); + bytes[index++] = (byte)(A2 >> j); + bytes[index++] = (byte)(A3 >> j); + } + } + + private void encryptBlock( + long[][] rk) + { + int r; + + // + // begin with a key addition + // + KeyAddition(rk[0]); + + // + // ROUNDS-1 ordinary rounds + // + for (r = 1; r < ROUNDS; r++) + { + Substitution(S); + ShiftRow(shifts0SC); + MixColumn(); + KeyAddition(rk[r]); + } + + // + // Last round is special: there is no MixColumn + // + Substitution(S); + ShiftRow(shifts0SC); + KeyAddition(rk[ROUNDS]); + } + + private void decryptBlock( + long[][] rk) + { + int r; + + // To decrypt: apply the inverse operations of the encrypt routine, + // in opposite order + // + // (KeyAddition is an involution: it 's equal to its inverse) + // (the inverse of Substitution with table S is Substitution with the inverse table of S) + // (the inverse of Shiftrow is Shiftrow over a suitable distance) + // + + // First the special round: + // without InvMixColumn + // with extra KeyAddition + // + KeyAddition(rk[ROUNDS]); + Substitution(Si); + ShiftRow(shifts1SC); + + // + // ROUNDS-1 ordinary rounds + // + for (r = ROUNDS-1; r > 0; r--) + { + KeyAddition(rk[r]); + InvMixColumn(); + Substitution(Si); + ShiftRow(shifts1SC); + } + + // + // End with the extra key addition + // + KeyAddition(rk[0]); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/SEEDEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/SEEDEngine.java new file mode 100644 index 000000000..b68630b71 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/SEEDEngine.java @@ -0,0 +1,345 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * Implementation of the SEED algorithm as described in RFC 4009 + */ +public class SEEDEngine + implements BlockCipher +{ + private final int BLOCK_SIZE = 16; + + private static final int[] SS0 = + { + 0x2989a1a8, 0x05858184, 0x16c6d2d4, 0x13c3d3d0, 0x14445054, 0x1d0d111c, 0x2c8ca0ac, 0x25052124, + 0x1d4d515c, 0x03434340, 0x18081018, 0x1e0e121c, 0x11415150, 0x3cccf0fc, 0x0acac2c8, 0x23436360, + 0x28082028, 0x04444044, 0x20002020, 0x1d8d919c, 0x20c0e0e0, 0x22c2e2e0, 0x08c8c0c8, 0x17071314, + 0x2585a1a4, 0x0f8f838c, 0x03030300, 0x3b4b7378, 0x3b8bb3b8, 0x13031310, 0x12c2d2d0, 0x2ecee2ec, + 0x30407070, 0x0c8c808c, 0x3f0f333c, 0x2888a0a8, 0x32023230, 0x1dcdd1dc, 0x36c6f2f4, 0x34447074, + 0x2ccce0ec, 0x15859194, 0x0b0b0308, 0x17475354, 0x1c4c505c, 0x1b4b5358, 0x3d8db1bc, 0x01010100, + 0x24042024, 0x1c0c101c, 0x33437370, 0x18889098, 0x10001010, 0x0cccc0cc, 0x32c2f2f0, 0x19c9d1d8, + 0x2c0c202c, 0x27c7e3e4, 0x32427270, 0x03838380, 0x1b8b9398, 0x11c1d1d0, 0x06868284, 0x09c9c1c8, + 0x20406060, 0x10405050, 0x2383a3a0, 0x2bcbe3e8, 0x0d0d010c, 0x3686b2b4, 0x1e8e929c, 0x0f4f434c, + 0x3787b3b4, 0x1a4a5258, 0x06c6c2c4, 0x38487078, 0x2686a2a4, 0x12021210, 0x2f8fa3ac, 0x15c5d1d4, + 0x21416160, 0x03c3c3c0, 0x3484b0b4, 0x01414140, 0x12425250, 0x3d4d717c, 0x0d8d818c, 0x08080008, + 0x1f0f131c, 0x19899198, 0x00000000, 0x19091118, 0x04040004, 0x13435350, 0x37c7f3f4, 0x21c1e1e0, + 0x3dcdf1fc, 0x36467274, 0x2f0f232c, 0x27072324, 0x3080b0b0, 0x0b8b8388, 0x0e0e020c, 0x2b8ba3a8, + 0x2282a2a0, 0x2e4e626c, 0x13839390, 0x0d4d414c, 0x29496168, 0x3c4c707c, 0x09090108, 0x0a0a0208, + 0x3f8fb3bc, 0x2fcfe3ec, 0x33c3f3f0, 0x05c5c1c4, 0x07878384, 0x14041014, 0x3ecef2fc, 0x24446064, + 0x1eced2dc, 0x2e0e222c, 0x0b4b4348, 0x1a0a1218, 0x06060204, 0x21012120, 0x2b4b6368, 0x26466264, + 0x02020200, 0x35c5f1f4, 0x12829290, 0x0a8a8288, 0x0c0c000c, 0x3383b3b0, 0x3e4e727c, 0x10c0d0d0, + 0x3a4a7278, 0x07474344, 0x16869294, 0x25c5e1e4, 0x26062224, 0x00808080, 0x2d8da1ac, 0x1fcfd3dc, + 0x2181a1a0, 0x30003030, 0x37073334, 0x2e8ea2ac, 0x36063234, 0x15051114, 0x22022220, 0x38083038, + 0x34c4f0f4, 0x2787a3a4, 0x05454144, 0x0c4c404c, 0x01818180, 0x29c9e1e8, 0x04848084, 0x17879394, + 0x35053134, 0x0bcbc3c8, 0x0ecec2cc, 0x3c0c303c, 0x31417170, 0x11011110, 0x07c7c3c4, 0x09898188, + 0x35457174, 0x3bcbf3f8, 0x1acad2d8, 0x38c8f0f8, 0x14849094, 0x19495158, 0x02828280, 0x04c4c0c4, + 0x3fcff3fc, 0x09494148, 0x39093138, 0x27476364, 0x00c0c0c0, 0x0fcfc3cc, 0x17c7d3d4, 0x3888b0b8, + 0x0f0f030c, 0x0e8e828c, 0x02424240, 0x23032320, 0x11819190, 0x2c4c606c, 0x1bcbd3d8, 0x2484a0a4, + 0x34043034, 0x31c1f1f0, 0x08484048, 0x02c2c2c0, 0x2f4f636c, 0x3d0d313c, 0x2d0d212c, 0x00404040, + 0x3e8eb2bc, 0x3e0e323c, 0x3c8cb0bc, 0x01c1c1c0, 0x2a8aa2a8, 0x3a8ab2b8, 0x0e4e424c, 0x15455154, + 0x3b0b3338, 0x1cccd0dc, 0x28486068, 0x3f4f737c, 0x1c8c909c, 0x18c8d0d8, 0x0a4a4248, 0x16465254, + 0x37477374, 0x2080a0a0, 0x2dcde1ec, 0x06464244, 0x3585b1b4, 0x2b0b2328, 0x25456164, 0x3acaf2f8, + 0x23c3e3e0, 0x3989b1b8, 0x3181b1b0, 0x1f8f939c, 0x1e4e525c, 0x39c9f1f8, 0x26c6e2e4, 0x3282b2b0, + 0x31013130, 0x2acae2e8, 0x2d4d616c, 0x1f4f535c, 0x24c4e0e4, 0x30c0f0f0, 0x0dcdc1cc, 0x08888088, + 0x16061214, 0x3a0a3238, 0x18485058, 0x14c4d0d4, 0x22426260, 0x29092128, 0x07070304, 0x33033330, + 0x28c8e0e8, 0x1b0b1318, 0x05050104, 0x39497178, 0x10809090, 0x2a4a6268, 0x2a0a2228, 0x1a8a9298 + }; + + private static final int[] SS1 = + { + + 0x38380830, 0xe828c8e0, 0x2c2d0d21, 0xa42686a2, 0xcc0fcfc3, 0xdc1eced2, 0xb03383b3, 0xb83888b0, + 0xac2f8fa3, 0x60204060, 0x54154551, 0xc407c7c3, 0x44044440, 0x6c2f4f63, 0x682b4b63, 0x581b4b53, + 0xc003c3c3, 0x60224262, 0x30330333, 0xb43585b1, 0x28290921, 0xa02080a0, 0xe022c2e2, 0xa42787a3, + 0xd013c3d3, 0x90118191, 0x10110111, 0x04060602, 0x1c1c0c10, 0xbc3c8cb0, 0x34360632, 0x480b4b43, + 0xec2fcfe3, 0x88088880, 0x6c2c4c60, 0xa82888a0, 0x14170713, 0xc404c4c0, 0x14160612, 0xf434c4f0, + 0xc002c2c2, 0x44054541, 0xe021c1e1, 0xd416c6d2, 0x3c3f0f33, 0x3c3d0d31, 0x8c0e8e82, 0x98188890, + 0x28280820, 0x4c0e4e42, 0xf436c6f2, 0x3c3e0e32, 0xa42585a1, 0xf839c9f1, 0x0c0d0d01, 0xdc1fcfd3, + 0xd818c8d0, 0x282b0b23, 0x64264662, 0x783a4a72, 0x24270723, 0x2c2f0f23, 0xf031c1f1, 0x70324272, + 0x40024242, 0xd414c4d0, 0x40014141, 0xc000c0c0, 0x70334373, 0x64274763, 0xac2c8ca0, 0x880b8b83, + 0xf437c7f3, 0xac2d8da1, 0x80008080, 0x1c1f0f13, 0xc80acac2, 0x2c2c0c20, 0xa82a8aa2, 0x34340430, + 0xd012c2d2, 0x080b0b03, 0xec2ecee2, 0xe829c9e1, 0x5c1d4d51, 0x94148490, 0x18180810, 0xf838c8f0, + 0x54174753, 0xac2e8ea2, 0x08080800, 0xc405c5c1, 0x10130313, 0xcc0dcdc1, 0x84068682, 0xb83989b1, + 0xfc3fcff3, 0x7c3d4d71, 0xc001c1c1, 0x30310131, 0xf435c5f1, 0x880a8a82, 0x682a4a62, 0xb03181b1, + 0xd011c1d1, 0x20200020, 0xd417c7d3, 0x00020202, 0x20220222, 0x04040400, 0x68284860, 0x70314171, + 0x04070703, 0xd81bcbd3, 0x9c1d8d91, 0x98198991, 0x60214161, 0xbc3e8eb2, 0xe426c6e2, 0x58194951, + 0xdc1dcdd1, 0x50114151, 0x90108090, 0xdc1cccd0, 0x981a8a92, 0xa02383a3, 0xa82b8ba3, 0xd010c0d0, + 0x80018181, 0x0c0f0f03, 0x44074743, 0x181a0a12, 0xe023c3e3, 0xec2ccce0, 0x8c0d8d81, 0xbc3f8fb3, + 0x94168692, 0x783b4b73, 0x5c1c4c50, 0xa02282a2, 0xa02181a1, 0x60234363, 0x20230323, 0x4c0d4d41, + 0xc808c8c0, 0x9c1e8e92, 0x9c1c8c90, 0x383a0a32, 0x0c0c0c00, 0x2c2e0e22, 0xb83a8ab2, 0x6c2e4e62, + 0x9c1f8f93, 0x581a4a52, 0xf032c2f2, 0x90128292, 0xf033c3f3, 0x48094941, 0x78384870, 0xcc0cccc0, + 0x14150511, 0xf83bcbf3, 0x70304070, 0x74354571, 0x7c3f4f73, 0x34350531, 0x10100010, 0x00030303, + 0x64244460, 0x6c2d4d61, 0xc406c6c2, 0x74344470, 0xd415c5d1, 0xb43484b0, 0xe82acae2, 0x08090901, + 0x74364672, 0x18190911, 0xfc3ecef2, 0x40004040, 0x10120212, 0xe020c0e0, 0xbc3d8db1, 0x04050501, + 0xf83acaf2, 0x00010101, 0xf030c0f0, 0x282a0a22, 0x5c1e4e52, 0xa82989a1, 0x54164652, 0x40034343, + 0x84058581, 0x14140410, 0x88098981, 0x981b8b93, 0xb03080b0, 0xe425c5e1, 0x48084840, 0x78394971, + 0x94178793, 0xfc3cccf0, 0x1c1e0e12, 0x80028282, 0x20210121, 0x8c0c8c80, 0x181b0b13, 0x5c1f4f53, + 0x74374773, 0x54144450, 0xb03282b2, 0x1c1d0d11, 0x24250521, 0x4c0f4f43, 0x00000000, 0x44064642, + 0xec2dcde1, 0x58184850, 0x50124252, 0xe82bcbe3, 0x7c3e4e72, 0xd81acad2, 0xc809c9c1, 0xfc3dcdf1, + 0x30300030, 0x94158591, 0x64254561, 0x3c3c0c30, 0xb43686b2, 0xe424c4e0, 0xb83b8bb3, 0x7c3c4c70, + 0x0c0e0e02, 0x50104050, 0x38390931, 0x24260622, 0x30320232, 0x84048480, 0x68294961, 0x90138393, + 0x34370733, 0xe427c7e3, 0x24240420, 0xa42484a0, 0xc80bcbc3, 0x50134353, 0x080a0a02, 0x84078783, + 0xd819c9d1, 0x4c0c4c40, 0x80038383, 0x8c0f8f83, 0xcc0ecec2, 0x383b0b33, 0x480a4a42, 0xb43787b3 + }; + + private static final int[] SS2 = + { + + 0xa1a82989, 0x81840585, 0xd2d416c6, 0xd3d013c3, 0x50541444, 0x111c1d0d, 0xa0ac2c8c, 0x21242505, + 0x515c1d4d, 0x43400343, 0x10181808, 0x121c1e0e, 0x51501141, 0xf0fc3ccc, 0xc2c80aca, 0x63602343, + 0x20282808, 0x40440444, 0x20202000, 0x919c1d8d, 0xe0e020c0, 0xe2e022c2, 0xc0c808c8, 0x13141707, + 0xa1a42585, 0x838c0f8f, 0x03000303, 0x73783b4b, 0xb3b83b8b, 0x13101303, 0xd2d012c2, 0xe2ec2ece, + 0x70703040, 0x808c0c8c, 0x333c3f0f, 0xa0a82888, 0x32303202, 0xd1dc1dcd, 0xf2f436c6, 0x70743444, + 0xe0ec2ccc, 0x91941585, 0x03080b0b, 0x53541747, 0x505c1c4c, 0x53581b4b, 0xb1bc3d8d, 0x01000101, + 0x20242404, 0x101c1c0c, 0x73703343, 0x90981888, 0x10101000, 0xc0cc0ccc, 0xf2f032c2, 0xd1d819c9, + 0x202c2c0c, 0xe3e427c7, 0x72703242, 0x83800383, 0x93981b8b, 0xd1d011c1, 0x82840686, 0xc1c809c9, + 0x60602040, 0x50501040, 0xa3a02383, 0xe3e82bcb, 0x010c0d0d, 0xb2b43686, 0x929c1e8e, 0x434c0f4f, + 0xb3b43787, 0x52581a4a, 0xc2c406c6, 0x70783848, 0xa2a42686, 0x12101202, 0xa3ac2f8f, 0xd1d415c5, + 0x61602141, 0xc3c003c3, 0xb0b43484, 0x41400141, 0x52501242, 0x717c3d4d, 0x818c0d8d, 0x00080808, + 0x131c1f0f, 0x91981989, 0x00000000, 0x11181909, 0x00040404, 0x53501343, 0xf3f437c7, 0xe1e021c1, + 0xf1fc3dcd, 0x72743646, 0x232c2f0f, 0x23242707, 0xb0b03080, 0x83880b8b, 0x020c0e0e, 0xa3a82b8b, + 0xa2a02282, 0x626c2e4e, 0x93901383, 0x414c0d4d, 0x61682949, 0x707c3c4c, 0x01080909, 0x02080a0a, + 0xb3bc3f8f, 0xe3ec2fcf, 0xf3f033c3, 0xc1c405c5, 0x83840787, 0x10141404, 0xf2fc3ece, 0x60642444, + 0xd2dc1ece, 0x222c2e0e, 0x43480b4b, 0x12181a0a, 0x02040606, 0x21202101, 0x63682b4b, 0x62642646, + 0x02000202, 0xf1f435c5, 0x92901282, 0x82880a8a, 0x000c0c0c, 0xb3b03383, 0x727c3e4e, 0xd0d010c0, + 0x72783a4a, 0x43440747, 0x92941686, 0xe1e425c5, 0x22242606, 0x80800080, 0xa1ac2d8d, 0xd3dc1fcf, + 0xa1a02181, 0x30303000, 0x33343707, 0xa2ac2e8e, 0x32343606, 0x11141505, 0x22202202, 0x30383808, + 0xf0f434c4, 0xa3a42787, 0x41440545, 0x404c0c4c, 0x81800181, 0xe1e829c9, 0x80840484, 0x93941787, + 0x31343505, 0xc3c80bcb, 0xc2cc0ece, 0x303c3c0c, 0x71703141, 0x11101101, 0xc3c407c7, 0x81880989, + 0x71743545, 0xf3f83bcb, 0xd2d81aca, 0xf0f838c8, 0x90941484, 0x51581949, 0x82800282, 0xc0c404c4, + 0xf3fc3fcf, 0x41480949, 0x31383909, 0x63642747, 0xc0c000c0, 0xc3cc0fcf, 0xd3d417c7, 0xb0b83888, + 0x030c0f0f, 0x828c0e8e, 0x42400242, 0x23202303, 0x91901181, 0x606c2c4c, 0xd3d81bcb, 0xa0a42484, + 0x30343404, 0xf1f031c1, 0x40480848, 0xc2c002c2, 0x636c2f4f, 0x313c3d0d, 0x212c2d0d, 0x40400040, + 0xb2bc3e8e, 0x323c3e0e, 0xb0bc3c8c, 0xc1c001c1, 0xa2a82a8a, 0xb2b83a8a, 0x424c0e4e, 0x51541545, + 0x33383b0b, 0xd0dc1ccc, 0x60682848, 0x737c3f4f, 0x909c1c8c, 0xd0d818c8, 0x42480a4a, 0x52541646, + 0x73743747, 0xa0a02080, 0xe1ec2dcd, 0x42440646, 0xb1b43585, 0x23282b0b, 0x61642545, 0xf2f83aca, + 0xe3e023c3, 0xb1b83989, 0xb1b03181, 0x939c1f8f, 0x525c1e4e, 0xf1f839c9, 0xe2e426c6, 0xb2b03282, + 0x31303101, 0xe2e82aca, 0x616c2d4d, 0x535c1f4f, 0xe0e424c4, 0xf0f030c0, 0xc1cc0dcd, 0x80880888, + 0x12141606, 0x32383a0a, 0x50581848, 0xd0d414c4, 0x62602242, 0x21282909, 0x03040707, 0x33303303, + 0xe0e828c8, 0x13181b0b, 0x01040505, 0x71783949, 0x90901080, 0x62682a4a, 0x22282a0a, 0x92981a8a + }; + + + private static final int[] SS3 = + { + + 0x08303838, 0xc8e0e828, 0x0d212c2d, 0x86a2a426, 0xcfc3cc0f, 0xced2dc1e, 0x83b3b033, 0x88b0b838, + 0x8fa3ac2f, 0x40606020, 0x45515415, 0xc7c3c407, 0x44404404, 0x4f636c2f, 0x4b63682b, 0x4b53581b, + 0xc3c3c003, 0x42626022, 0x03333033, 0x85b1b435, 0x09212829, 0x80a0a020, 0xc2e2e022, 0x87a3a427, + 0xc3d3d013, 0x81919011, 0x01111011, 0x06020406, 0x0c101c1c, 0x8cb0bc3c, 0x06323436, 0x4b43480b, + 0xcfe3ec2f, 0x88808808, 0x4c606c2c, 0x88a0a828, 0x07131417, 0xc4c0c404, 0x06121416, 0xc4f0f434, + 0xc2c2c002, 0x45414405, 0xc1e1e021, 0xc6d2d416, 0x0f333c3f, 0x0d313c3d, 0x8e828c0e, 0x88909818, + 0x08202828, 0x4e424c0e, 0xc6f2f436, 0x0e323c3e, 0x85a1a425, 0xc9f1f839, 0x0d010c0d, 0xcfd3dc1f, + 0xc8d0d818, 0x0b23282b, 0x46626426, 0x4a72783a, 0x07232427, 0x0f232c2f, 0xc1f1f031, 0x42727032, + 0x42424002, 0xc4d0d414, 0x41414001, 0xc0c0c000, 0x43737033, 0x47636427, 0x8ca0ac2c, 0x8b83880b, + 0xc7f3f437, 0x8da1ac2d, 0x80808000, 0x0f131c1f, 0xcac2c80a, 0x0c202c2c, 0x8aa2a82a, 0x04303434, + 0xc2d2d012, 0x0b03080b, 0xcee2ec2e, 0xc9e1e829, 0x4d515c1d, 0x84909414, 0x08101818, 0xc8f0f838, + 0x47535417, 0x8ea2ac2e, 0x08000808, 0xc5c1c405, 0x03131013, 0xcdc1cc0d, 0x86828406, 0x89b1b839, + 0xcff3fc3f, 0x4d717c3d, 0xc1c1c001, 0x01313031, 0xc5f1f435, 0x8a82880a, 0x4a62682a, 0x81b1b031, + 0xc1d1d011, 0x00202020, 0xc7d3d417, 0x02020002, 0x02222022, 0x04000404, 0x48606828, 0x41717031, + 0x07030407, 0xcbd3d81b, 0x8d919c1d, 0x89919819, 0x41616021, 0x8eb2bc3e, 0xc6e2e426, 0x49515819, + 0xcdd1dc1d, 0x41515011, 0x80909010, 0xccd0dc1c, 0x8a92981a, 0x83a3a023, 0x8ba3a82b, 0xc0d0d010, + 0x81818001, 0x0f030c0f, 0x47434407, 0x0a12181a, 0xc3e3e023, 0xcce0ec2c, 0x8d818c0d, 0x8fb3bc3f, + 0x86929416, 0x4b73783b, 0x4c505c1c, 0x82a2a022, 0x81a1a021, 0x43636023, 0x03232023, 0x4d414c0d, + 0xc8c0c808, 0x8e929c1e, 0x8c909c1c, 0x0a32383a, 0x0c000c0c, 0x0e222c2e, 0x8ab2b83a, 0x4e626c2e, + 0x8f939c1f, 0x4a52581a, 0xc2f2f032, 0x82929012, 0xc3f3f033, 0x49414809, 0x48707838, 0xccc0cc0c, + 0x05111415, 0xcbf3f83b, 0x40707030, 0x45717435, 0x4f737c3f, 0x05313435, 0x00101010, 0x03030003, + 0x44606424, 0x4d616c2d, 0xc6c2c406, 0x44707434, 0xc5d1d415, 0x84b0b434, 0xcae2e82a, 0x09010809, + 0x46727436, 0x09111819, 0xcef2fc3e, 0x40404000, 0x02121012, 0xc0e0e020, 0x8db1bc3d, 0x05010405, + 0xcaf2f83a, 0x01010001, 0xc0f0f030, 0x0a22282a, 0x4e525c1e, 0x89a1a829, 0x46525416, 0x43434003, + 0x85818405, 0x04101414, 0x89818809, 0x8b93981b, 0x80b0b030, 0xc5e1e425, 0x48404808, 0x49717839, + 0x87939417, 0xccf0fc3c, 0x0e121c1e, 0x82828002, 0x01212021, 0x8c808c0c, 0x0b13181b, 0x4f535c1f, + 0x47737437, 0x44505414, 0x82b2b032, 0x0d111c1d, 0x05212425, 0x4f434c0f, 0x00000000, 0x46424406, + 0xcde1ec2d, 0x48505818, 0x42525012, 0xcbe3e82b, 0x4e727c3e, 0xcad2d81a, 0xc9c1c809, 0xcdf1fc3d, + 0x00303030, 0x85919415, 0x45616425, 0x0c303c3c, 0x86b2b436, 0xc4e0e424, 0x8bb3b83b, 0x4c707c3c, + 0x0e020c0e, 0x40505010, 0x09313839, 0x06222426, 0x02323032, 0x84808404, 0x49616829, 0x83939013, + 0x07333437, 0xc7e3e427, 0x04202424, 0x84a0a424, 0xcbc3c80b, 0x43535013, 0x0a02080a, 0x87838407, + 0xc9d1d819, 0x4c404c0c, 0x83838003, 0x8f838c0f, 0xcec2cc0e, 0x0b33383b, 0x4a42480a, 0x87b3b437 + }; + + + private static final int[] KC = + { + 0x9e3779b9, 0x3c6ef373, 0x78dde6e6, 0xf1bbcdcc, + 0xe3779b99, 0xc6ef3733, 0x8dde6e67, 0x1bbcdccf, + 0x3779b99e, 0x6ef3733c, 0xdde6e678, 0xbbcdccf1, + 0x779b99e3, 0xef3733c6, 0xde6e678d, 0xbcdccf1b + }; + + private int[] wKey; + private boolean forEncryption; + + public void init(boolean forEncryption, CipherParameters params) throws IllegalArgumentException + { + this.forEncryption = forEncryption; + wKey = createWorkingKey(((KeyParameter)params).getKey()); + } + + public String getAlgorithmName() + { + return "SEED"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock(byte[] in, int inOff, byte[] out, int outOff) throws DataLengthException, IllegalStateException + { + if (wKey == null) + { + throw new IllegalStateException("SEED engine not initialised"); + } + + if (inOff + BLOCK_SIZE > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if (outOff + BLOCK_SIZE > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + long l = bytesToLong(in, inOff + 0); + long r = bytesToLong(in, inOff + 8); + + if (forEncryption) + { + for (int i = 0; i < 16; i++) + { + long nl = r; + + r = l ^ F(wKey[2 * i], wKey[(2 * i) + 1], r); + l = nl; + } + } + else + { + for (int i = 15; i >= 0; i--) + { + long nl = r; + + r = l ^ F(wKey[2 * i], wKey[(2 * i) + 1], r); + l = nl; + } + } + + longToBytes(out, outOff + 0, r); + longToBytes(out, outOff + 8, l); + + return BLOCK_SIZE; + } + + public void reset() + { + } + + private int[] createWorkingKey(byte[] inKey) + { + int[] key = new int[32]; + long lower = bytesToLong(inKey, 0); + long upper = bytesToLong(inKey, 8); + + int key0 = extractW0(lower); + int key1 = extractW1(lower); + int key2 = extractW0(upper); + int key3 = extractW1(upper); + + for (int i = 0; i < 16; i++) + { + key[2 * i] = G(key0 + key2 - KC[i]); + key[2 * i + 1] = G(key1 - key3 + KC[i]); + + if (i % 2 == 0) + { + lower = rotateRight8(lower); + key0 = extractW0(lower); + key1 = extractW1(lower); + } + else + { + upper = rotateLeft8(upper); + key2 = extractW0(upper); + key3 = extractW1(upper); + } + } + + return key; + } + + private int extractW1(long lVal) + { + return (int)lVal; + } + + private int extractW0(long lVal) + { + return (int)(lVal >> 32); + } + + private long rotateLeft8(long x) + { + return (x << 8) | (x >>> 56); + } + + private long rotateRight8(long x) + { + return (x >>> 8) | (x << 56); + } + + private long bytesToLong( + byte[] src, + int srcOff) + { + long word = 0; + + for (int i = 0; i <= 7; i++) + { + word = (word << 8) + (src[i + srcOff] & 0xff); + } + + return word; + } + + private void longToBytes( + byte[] dest, + int destOff, + long value) + { + for (int i = 0; i < 8; i++) + { + dest[i + destOff] = (byte)(value >> ((7 - i) * 8)); + } + } + + private int G(int x) + { + return SS0[x & 0xff] ^ SS1[(x >> 8) & 0xff] ^ SS2[(x >> 16) & 0xff] ^ SS3[(x >> 24) & 0xff]; + } + + private long F(int ki0, int ki1, long r) + { + int r0 = (int)(r >> 32); + int r1 = (int)r; + int rd1 = phaseCalc2(r0, ki0, r1, ki1); + int rd0 = rd1 + phaseCalc1(r0, ki0, r1, ki1); + + return ((long)rd0 << 32) | (rd1 & 0xffffffffL); + } + + private int phaseCalc1(int r0, int ki0, int r1, int ki1) + { + return G(G((r0 ^ ki0) ^ (r1 ^ ki1)) + (r0 ^ ki0)); + } + + private int phaseCalc2(int r0, int ki0, int r1, int ki1) + { + return G(phaseCalc1(r0, ki0, r1, ki1) + G((r0 ^ ki0) ^ (r1 ^ ki1))); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/SEEDWrapEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/SEEDWrapEngine.java new file mode 100644 index 000000000..ea1998a7c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/SEEDWrapEngine.java @@ -0,0 +1,15 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +/** + * An implementation of the SEED key wrapper based on RFC 4010/RFC 3394. + *

+ * For further details see: http://www.ietf.org/rfc/rfc4010.txt. + */ +public class SEEDWrapEngine + extends RFC3394WrapEngine +{ + public SEEDWrapEngine() + { + super(new SEEDEngine()); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/Salsa20Engine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/Salsa20Engine.java new file mode 100644 index 000000000..f78bd28ec --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/Salsa20Engine.java @@ -0,0 +1,362 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.MaxBytesExceededException; +import com.google.bitcoin.bouncycastle.crypto.StreamCipher; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; +import com.google.bitcoin.bouncycastle.util.Strings; + +/** + * Implementation of Daniel J. Bernstein's Salsa20 stream cipher, Snuffle 2005 + */ + +public class Salsa20Engine + implements StreamCipher +{ + /** Constants */ + private final static int stateSize = 16; // 16, 32 bit ints = 64 bytes + + private final static byte[] + sigma = Strings.toByteArray("expand 32-byte k"), + tau = Strings.toByteArray("expand 16-byte k"); + + /* + * variables to hold the state of the engine + * during encryption and decryption + */ + private int index = 0; + private int[] engineState = new int[stateSize]; // state + private int[] x = new int[stateSize] ; // internal buffer + private byte[] keyStream = new byte[stateSize * 4], // expanded state, 64 bytes + workingKey = null, + workingIV = null; + private boolean initialised = false; + + /* + * internal counter + */ + private int cW0, cW1, cW2; + + + /** + * initialise a Salsa20 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + /* + * Salsa20 encryption and decryption is completely + * symmetrical, so the 'forEncryption' is + * irrelevant. (Like 90% of stream ciphers) + */ + + if (!(params instanceof ParametersWithIV)) + { + throw new IllegalArgumentException("Salsa20 Init parameters must include an IV"); + } + + ParametersWithIV ivParams = (ParametersWithIV) params; + + byte[] iv = ivParams.getIV(); + + if (iv == null || iv.length != 8) + { + throw new IllegalArgumentException("Salsa20 requires exactly 8 bytes of IV"); + } + + if (!(ivParams.getParameters() instanceof KeyParameter)) + { + throw new IllegalArgumentException("Salsa20 Init parameters must include a key"); + } + + KeyParameter key = (KeyParameter) ivParams.getParameters(); + + workingKey = key.getKey(); + workingIV = iv; + + setKey(workingKey, workingIV); + } + + public String getAlgorithmName() + { + return "Salsa20"; + } + + public byte returnByte(byte in) + { + if (limitExceeded()) + { + throw new MaxBytesExceededException("2^70 byte limit per IV; Change IV"); + } + + if (index == 0) + { + salsa20WordToByte(engineState, keyStream); + engineState[8]++; + if (engineState[8] == 0) + { + engineState[9]++; + } + } + byte out = (byte)(keyStream[index]^in); + index = (index + 1) & 63; + + return out; + } + + public void processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + { + if (!initialised) + { + throw new IllegalStateException(getAlgorithmName()+" not initialised"); + } + + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (limitExceeded(len)) + { + throw new MaxBytesExceededException("2^70 byte limit per IV would be exceeded; Change IV"); + } + + for (int i = 0; i < len; i++) + { + if (index == 0) + { + salsa20WordToByte(engineState, keyStream); + engineState[8]++; + if (engineState[8] == 0) + { + engineState[9]++; + } + } + out[i+outOff] = (byte)(keyStream[index]^in[i+inOff]); + index = (index + 1) & 63; + } + } + + public void reset() + { + setKey(workingKey, workingIV); + } + + // Private implementation + + private void setKey(byte[] keyBytes, byte[] ivBytes) + { + workingKey = keyBytes; + workingIV = ivBytes; + + index = 0; + resetCounter(); + int offset = 0; + byte[] constants; + + // Key + engineState[1] = byteToIntLittle(workingKey, 0); + engineState[2] = byteToIntLittle(workingKey, 4); + engineState[3] = byteToIntLittle(workingKey, 8); + engineState[4] = byteToIntLittle(workingKey, 12); + + if (workingKey.length == 32) + { + constants = sigma; + offset = 16; + } + else + { + constants = tau; + } + + engineState[11] = byteToIntLittle(workingKey, offset); + engineState[12] = byteToIntLittle(workingKey, offset+4); + engineState[13] = byteToIntLittle(workingKey, offset+8); + engineState[14] = byteToIntLittle(workingKey, offset+12); + engineState[0 ] = byteToIntLittle(constants, 0); + engineState[5 ] = byteToIntLittle(constants, 4); + engineState[10] = byteToIntLittle(constants, 8); + engineState[15] = byteToIntLittle(constants, 12); + + // IV + engineState[6] = byteToIntLittle(workingIV, 0); + engineState[7] = byteToIntLittle(workingIV, 4); + engineState[8] = engineState[9] = 0; + + initialised = true; + } + + /** + * Salsa20 function + * + * @param input input data + * + * @return keystream + */ + private void salsa20WordToByte(int[] input, byte[] output) + { + System.arraycopy(input, 0, x, 0, input.length); + + for (int i = 0; i < 10; i++) + { + x[ 4] ^= rotl((x[ 0]+x[12]), 7); + x[ 8] ^= rotl((x[ 4]+x[ 0]), 9); + x[12] ^= rotl((x[ 8]+x[ 4]),13); + x[ 0] ^= rotl((x[12]+x[ 8]),18); + x[ 9] ^= rotl((x[ 5]+x[ 1]), 7); + x[13] ^= rotl((x[ 9]+x[ 5]), 9); + x[ 1] ^= rotl((x[13]+x[ 9]),13); + x[ 5] ^= rotl((x[ 1]+x[13]),18); + x[14] ^= rotl((x[10]+x[ 6]), 7); + x[ 2] ^= rotl((x[14]+x[10]), 9); + x[ 6] ^= rotl((x[ 2]+x[14]),13); + x[10] ^= rotl((x[ 6]+x[ 2]),18); + x[ 3] ^= rotl((x[15]+x[11]), 7); + x[ 7] ^= rotl((x[ 3]+x[15]), 9); + x[11] ^= rotl((x[ 7]+x[ 3]),13); + x[15] ^= rotl((x[11]+x[ 7]),18); + x[ 1] ^= rotl((x[ 0]+x[ 3]), 7); + x[ 2] ^= rotl((x[ 1]+x[ 0]), 9); + x[ 3] ^= rotl((x[ 2]+x[ 1]),13); + x[ 0] ^= rotl((x[ 3]+x[ 2]),18); + x[ 6] ^= rotl((x[ 5]+x[ 4]), 7); + x[ 7] ^= rotl((x[ 6]+x[ 5]), 9); + x[ 4] ^= rotl((x[ 7]+x[ 6]),13); + x[ 5] ^= rotl((x[ 4]+x[ 7]),18); + x[11] ^= rotl((x[10]+x[ 9]), 7); + x[ 8] ^= rotl((x[11]+x[10]), 9); + x[ 9] ^= rotl((x[ 8]+x[11]),13); + x[10] ^= rotl((x[ 9]+x[ 8]),18); + x[12] ^= rotl((x[15]+x[14]), 7); + x[13] ^= rotl((x[12]+x[15]), 9); + x[14] ^= rotl((x[13]+x[12]),13); + x[15] ^= rotl((x[14]+x[13]),18); + } + + int offset = 0; + for (int i = 0; i < stateSize; i++) + { + intToByteLittle(x[i] + input[i], output, offset); + offset += 4; + } + + for (int i = stateSize; i < x.length; i++) + { + intToByteLittle(x[i], output, offset); + offset += 4; + } + } + + /** + * 32 bit word to 4 byte array in little endian order + * + * @param x value to 'unpack' + * + * @return value of x expressed as a byte[] array in little endian order + */ + private byte[] intToByteLittle(int x, byte[] out, int off) + { + out[off] = (byte)x; + out[off + 1] = (byte)(x >>> 8); + out[off + 2] = (byte)(x >>> 16); + out[off + 3] = (byte)(x >>> 24); + return out; + } + + /** + * Rotate left + * + * @param x value to rotate + * @param y amount to rotate x + * + * @return rotated x + */ + private int rotl(int x, int y) + { + return (x << y) | (x >>> -y); + } + + /** + * Pack byte[] array into an int in little endian order + * + * @param x byte array to 'pack' + * @param offset only x[offset]..x[offset+3] will be packed + * + * @return x[offset]..x[offset+3] 'packed' into an int in little-endian order + */ + private int byteToIntLittle(byte[] x, int offset) + { + return ((x[offset] & 255)) | + ((x[offset + 1] & 255) << 8) | + ((x[offset + 2] & 255) << 16) | + (x[offset + 3] << 24); + } + + private void resetCounter() + { + cW0 = 0; + cW1 = 0; + cW2 = 0; + } + + private boolean limitExceeded() + { + cW0++; + if (cW0 == 0) + { + cW1++; + if (cW1 == 0) + { + cW2++; + return (cW2 & 0x20) != 0; // 2^(32 + 32 + 6) + } + } + + return false; + } + + /* + * this relies on the fact len will always be positive. + */ + private boolean limitExceeded(int len) + { + if (cW0 >= 0) + { + cW0 += len; + } + else + { + cW0 += len; + if (cW0 >= 0) + { + cW1++; + if (cW1 == 0) + { + cW2++; + return (cW2 & 0x20) != 0; // 2^(32 + 32 + 6) + } + } + } + + return false; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/SerpentEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/SerpentEngine.java new file mode 100644 index 000000000..3cedb3b60 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/SerpentEngine.java @@ -0,0 +1,782 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * Serpent is a 128-bit 32-round block cipher with variable key lengths, + * including 128, 192 and 256 bit keys conjectured to be at least as + * secure as three-key triple-DES. + *

+ * Serpent was designed by Ross Anderson, Eli Biham and Lars Knudsen as a + * candidate algorithm for the NIST AES Quest.> + *

+ * For full details see the The Serpent home page + */ +public class SerpentEngine + implements BlockCipher +{ + private static final int BLOCK_SIZE = 16; + + static final int ROUNDS = 32; + static final int PHI = 0x9E3779B9; // (sqrt(5) - 1) * 2**31 + + private boolean encrypting; + private int[] wKey; + + private int X0, X1, X2, X3; // registers + + /** + * initialise a Serpent cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + this.encrypting = encrypting; + this.wKey = makeWorkingKey(((KeyParameter)params).getKey()); + return; + } + + throw new IllegalArgumentException("invalid parameter passed to Serpent init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "Serpent"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + /** + * Process one block of input from the array in and write it to + * the out array. + * + * @param in the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param out the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public final int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (wKey == null) + { + throw new IllegalStateException("Serpent not initialised"); + } + + if ((inOff + BLOCK_SIZE) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + BLOCK_SIZE) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (encrypting) + { + encryptBlock(in, inOff, out, outOff); + } + else + { + decryptBlock(in, inOff, out, outOff); + } + + return BLOCK_SIZE; + } + + public void reset() + { + } + + /** + * Expand a user-supplied key material into a session key. + * + * @param key The user-key bytes (multiples of 4) to use. + * @exception IllegalArgumentException + */ + private int[] makeWorkingKey( + byte[] key) + throws IllegalArgumentException + { + // + // pad key to 256 bits + // + int[] kPad = new int[16]; + int off = 0; + int length = 0; + + for (off = key.length - 4; off > 0; off -= 4) + { + kPad[length++] = bytesToWord(key, off); + } + + if (off == 0) + { + kPad[length++] = bytesToWord(key, 0); + if (length < 8) + { + kPad[length] = 1; + } + } + else + { + throw new IllegalArgumentException("key must be a multiple of 4 bytes"); + } + + // + // expand the padded key up to 33 x 128 bits of key material + // + int amount = (ROUNDS + 1) * 4; + int[] w = new int[amount]; + + // + // compute w0 to w7 from w-8 to w-1 + // + for (int i = 8; i < 16; i++) + { + kPad[i] = rotateLeft(kPad[i - 8] ^ kPad[i - 5] ^ kPad[i - 3] ^ kPad[i - 1] ^ PHI ^ (i - 8), 11); + } + + System.arraycopy(kPad, 8, w, 0, 8); + + // + // compute w8 to w136 + // + for (int i = 8; i < amount; i++) + { + w[i] = rotateLeft(w[i - 8] ^ w[i - 5] ^ w[i - 3] ^ w[i - 1] ^ PHI ^ i, 11); + } + + // + // create the working keys by processing w with the Sbox and IP + // + sb3(w[0], w[1], w[2], w[3]); + w[0] = X0; w[1] = X1; w[2] = X2; w[3] = X3; + sb2(w[4], w[5], w[6], w[7]); + w[4] = X0; w[5] = X1; w[6] = X2; w[7] = X3; + sb1(w[8], w[9], w[10], w[11]); + w[8] = X0; w[9] = X1; w[10] = X2; w[11] = X3; + sb0(w[12], w[13], w[14], w[15]); + w[12] = X0; w[13] = X1; w[14] = X2; w[15] = X3; + sb7(w[16], w[17], w[18], w[19]); + w[16] = X0; w[17] = X1; w[18] = X2; w[19] = X3; + sb6(w[20], w[21], w[22], w[23]); + w[20] = X0; w[21] = X1; w[22] = X2; w[23] = X3; + sb5(w[24], w[25], w[26], w[27]); + w[24] = X0; w[25] = X1; w[26] = X2; w[27] = X3; + sb4(w[28], w[29], w[30], w[31]); + w[28] = X0; w[29] = X1; w[30] = X2; w[31] = X3; + sb3(w[32], w[33], w[34], w[35]); + w[32] = X0; w[33] = X1; w[34] = X2; w[35] = X3; + sb2(w[36], w[37], w[38], w[39]); + w[36] = X0; w[37] = X1; w[38] = X2; w[39] = X3; + sb1(w[40], w[41], w[42], w[43]); + w[40] = X0; w[41] = X1; w[42] = X2; w[43] = X3; + sb0(w[44], w[45], w[46], w[47]); + w[44] = X0; w[45] = X1; w[46] = X2; w[47] = X3; + sb7(w[48], w[49], w[50], w[51]); + w[48] = X0; w[49] = X1; w[50] = X2; w[51] = X3; + sb6(w[52], w[53], w[54], w[55]); + w[52] = X0; w[53] = X1; w[54] = X2; w[55] = X3; + sb5(w[56], w[57], w[58], w[59]); + w[56] = X0; w[57] = X1; w[58] = X2; w[59] = X3; + sb4(w[60], w[61], w[62], w[63]); + w[60] = X0; w[61] = X1; w[62] = X2; w[63] = X3; + sb3(w[64], w[65], w[66], w[67]); + w[64] = X0; w[65] = X1; w[66] = X2; w[67] = X3; + sb2(w[68], w[69], w[70], w[71]); + w[68] = X0; w[69] = X1; w[70] = X2; w[71] = X3; + sb1(w[72], w[73], w[74], w[75]); + w[72] = X0; w[73] = X1; w[74] = X2; w[75] = X3; + sb0(w[76], w[77], w[78], w[79]); + w[76] = X0; w[77] = X1; w[78] = X2; w[79] = X3; + sb7(w[80], w[81], w[82], w[83]); + w[80] = X0; w[81] = X1; w[82] = X2; w[83] = X3; + sb6(w[84], w[85], w[86], w[87]); + w[84] = X0; w[85] = X1; w[86] = X2; w[87] = X3; + sb5(w[88], w[89], w[90], w[91]); + w[88] = X0; w[89] = X1; w[90] = X2; w[91] = X3; + sb4(w[92], w[93], w[94], w[95]); + w[92] = X0; w[93] = X1; w[94] = X2; w[95] = X3; + sb3(w[96], w[97], w[98], w[99]); + w[96] = X0; w[97] = X1; w[98] = X2; w[99] = X3; + sb2(w[100], w[101], w[102], w[103]); + w[100] = X0; w[101] = X1; w[102] = X2; w[103] = X3; + sb1(w[104], w[105], w[106], w[107]); + w[104] = X0; w[105] = X1; w[106] = X2; w[107] = X3; + sb0(w[108], w[109], w[110], w[111]); + w[108] = X0; w[109] = X1; w[110] = X2; w[111] = X3; + sb7(w[112], w[113], w[114], w[115]); + w[112] = X0; w[113] = X1; w[114] = X2; w[115] = X3; + sb6(w[116], w[117], w[118], w[119]); + w[116] = X0; w[117] = X1; w[118] = X2; w[119] = X3; + sb5(w[120], w[121], w[122], w[123]); + w[120] = X0; w[121] = X1; w[122] = X2; w[123] = X3; + sb4(w[124], w[125], w[126], w[127]); + w[124] = X0; w[125] = X1; w[126] = X2; w[127] = X3; + sb3(w[128], w[129], w[130], w[131]); + w[128] = X0; w[129] = X1; w[130] = X2; w[131] = X3; + + return w; + } + + private int rotateLeft( + int x, + int bits) + { + return (x << bits) | (x >>> -bits); + } + + private int rotateRight( + int x, + int bits) + { + return (x >>> bits) | (x << -bits); + } + + private int bytesToWord( + byte[] src, + int srcOff) + { + return (((src[srcOff] & 0xff) << 24) | ((src[srcOff + 1] & 0xff) << 16) | + ((src[srcOff + 2] & 0xff) << 8) | ((src[srcOff + 3] & 0xff))); + } + + private void wordToBytes( + int word, + byte[] dst, + int dstOff) + { + dst[dstOff + 3] = (byte)(word); + dst[dstOff + 2] = (byte)(word >>> 8); + dst[dstOff + 1] = (byte)(word >>> 16); + dst[dstOff] = (byte)(word >>> 24); + } + + /** + * Encrypt one block of plaintext. + * + * @param in the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param out the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + */ + private void encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + X3 = bytesToWord(in, inOff); + X2 = bytesToWord(in, inOff + 4); + X1 = bytesToWord(in, inOff + 8); + X0 = bytesToWord(in, inOff + 12); + + sb0(wKey[0] ^ X0, wKey[1] ^ X1, wKey[2] ^ X2, wKey[3] ^ X3); LT(); + sb1(wKey[4] ^ X0, wKey[5] ^ X1, wKey[6] ^ X2, wKey[7] ^ X3); LT(); + sb2(wKey[8] ^ X0, wKey[9] ^ X1, wKey[10] ^ X2, wKey[11] ^ X3); LT(); + sb3(wKey[12] ^ X0, wKey[13] ^ X1, wKey[14] ^ X2, wKey[15] ^ X3); LT(); + sb4(wKey[16] ^ X0, wKey[17] ^ X1, wKey[18] ^ X2, wKey[19] ^ X3); LT(); + sb5(wKey[20] ^ X0, wKey[21] ^ X1, wKey[22] ^ X2, wKey[23] ^ X3); LT(); + sb6(wKey[24] ^ X0, wKey[25] ^ X1, wKey[26] ^ X2, wKey[27] ^ X3); LT(); + sb7(wKey[28] ^ X0, wKey[29] ^ X1, wKey[30] ^ X2, wKey[31] ^ X3); LT(); + sb0(wKey[32] ^ X0, wKey[33] ^ X1, wKey[34] ^ X2, wKey[35] ^ X3); LT(); + sb1(wKey[36] ^ X0, wKey[37] ^ X1, wKey[38] ^ X2, wKey[39] ^ X3); LT(); + sb2(wKey[40] ^ X0, wKey[41] ^ X1, wKey[42] ^ X2, wKey[43] ^ X3); LT(); + sb3(wKey[44] ^ X0, wKey[45] ^ X1, wKey[46] ^ X2, wKey[47] ^ X3); LT(); + sb4(wKey[48] ^ X0, wKey[49] ^ X1, wKey[50] ^ X2, wKey[51] ^ X3); LT(); + sb5(wKey[52] ^ X0, wKey[53] ^ X1, wKey[54] ^ X2, wKey[55] ^ X3); LT(); + sb6(wKey[56] ^ X0, wKey[57] ^ X1, wKey[58] ^ X2, wKey[59] ^ X3); LT(); + sb7(wKey[60] ^ X0, wKey[61] ^ X1, wKey[62] ^ X2, wKey[63] ^ X3); LT(); + sb0(wKey[64] ^ X0, wKey[65] ^ X1, wKey[66] ^ X2, wKey[67] ^ X3); LT(); + sb1(wKey[68] ^ X0, wKey[69] ^ X1, wKey[70] ^ X2, wKey[71] ^ X3); LT(); + sb2(wKey[72] ^ X0, wKey[73] ^ X1, wKey[74] ^ X2, wKey[75] ^ X3); LT(); + sb3(wKey[76] ^ X0, wKey[77] ^ X1, wKey[78] ^ X2, wKey[79] ^ X3); LT(); + sb4(wKey[80] ^ X0, wKey[81] ^ X1, wKey[82] ^ X2, wKey[83] ^ X3); LT(); + sb5(wKey[84] ^ X0, wKey[85] ^ X1, wKey[86] ^ X2, wKey[87] ^ X3); LT(); + sb6(wKey[88] ^ X0, wKey[89] ^ X1, wKey[90] ^ X2, wKey[91] ^ X3); LT(); + sb7(wKey[92] ^ X0, wKey[93] ^ X1, wKey[94] ^ X2, wKey[95] ^ X3); LT(); + sb0(wKey[96] ^ X0, wKey[97] ^ X1, wKey[98] ^ X2, wKey[99] ^ X3); LT(); + sb1(wKey[100] ^ X0, wKey[101] ^ X1, wKey[102] ^ X2, wKey[103] ^ X3); LT(); + sb2(wKey[104] ^ X0, wKey[105] ^ X1, wKey[106] ^ X2, wKey[107] ^ X3); LT(); + sb3(wKey[108] ^ X0, wKey[109] ^ X1, wKey[110] ^ X2, wKey[111] ^ X3); LT(); + sb4(wKey[112] ^ X0, wKey[113] ^ X1, wKey[114] ^ X2, wKey[115] ^ X3); LT(); + sb5(wKey[116] ^ X0, wKey[117] ^ X1, wKey[118] ^ X2, wKey[119] ^ X3); LT(); + sb6(wKey[120] ^ X0, wKey[121] ^ X1, wKey[122] ^ X2, wKey[123] ^ X3); LT(); + sb7(wKey[124] ^ X0, wKey[125] ^ X1, wKey[126] ^ X2, wKey[127] ^ X3); + + wordToBytes(wKey[131] ^ X3, out, outOff); + wordToBytes(wKey[130] ^ X2, out, outOff + 4); + wordToBytes(wKey[129] ^ X1, out, outOff + 8); + wordToBytes(wKey[128] ^ X0, out, outOff + 12); + } + + /** + * Decrypt one block of ciphertext. + * + * @param in the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param out the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + */ + private void decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + X3 = wKey[131] ^ bytesToWord(in, inOff); + X2 = wKey[130] ^ bytesToWord(in, inOff + 4); + X1 = wKey[129] ^ bytesToWord(in, inOff + 8); + X0 = wKey[128] ^ bytesToWord(in, inOff + 12); + + ib7(X0, X1, X2, X3); + X0 ^= wKey[124]; X1 ^= wKey[125]; X2 ^= wKey[126]; X3 ^= wKey[127]; + inverseLT(); ib6(X0, X1, X2, X3); + X0 ^= wKey[120]; X1 ^= wKey[121]; X2 ^= wKey[122]; X3 ^= wKey[123]; + inverseLT(); ib5(X0, X1, X2, X3); + X0 ^= wKey[116]; X1 ^= wKey[117]; X2 ^= wKey[118]; X3 ^= wKey[119]; + inverseLT(); ib4(X0, X1, X2, X3); + X0 ^= wKey[112]; X1 ^= wKey[113]; X2 ^= wKey[114]; X3 ^= wKey[115]; + inverseLT(); ib3(X0, X1, X2, X3); + X0 ^= wKey[108]; X1 ^= wKey[109]; X2 ^= wKey[110]; X3 ^= wKey[111]; + inverseLT(); ib2(X0, X1, X2, X3); + X0 ^= wKey[104]; X1 ^= wKey[105]; X2 ^= wKey[106]; X3 ^= wKey[107]; + inverseLT(); ib1(X0, X1, X2, X3); + X0 ^= wKey[100]; X1 ^= wKey[101]; X2 ^= wKey[102]; X3 ^= wKey[103]; + inverseLT(); ib0(X0, X1, X2, X3); + X0 ^= wKey[96]; X1 ^= wKey[97]; X2 ^= wKey[98]; X3 ^= wKey[99]; + inverseLT(); ib7(X0, X1, X2, X3); + X0 ^= wKey[92]; X1 ^= wKey[93]; X2 ^= wKey[94]; X3 ^= wKey[95]; + inverseLT(); ib6(X0, X1, X2, X3); + X0 ^= wKey[88]; X1 ^= wKey[89]; X2 ^= wKey[90]; X3 ^= wKey[91]; + inverseLT(); ib5(X0, X1, X2, X3); + X0 ^= wKey[84]; X1 ^= wKey[85]; X2 ^= wKey[86]; X3 ^= wKey[87]; + inverseLT(); ib4(X0, X1, X2, X3); + X0 ^= wKey[80]; X1 ^= wKey[81]; X2 ^= wKey[82]; X3 ^= wKey[83]; + inverseLT(); ib3(X0, X1, X2, X3); + X0 ^= wKey[76]; X1 ^= wKey[77]; X2 ^= wKey[78]; X3 ^= wKey[79]; + inverseLT(); ib2(X0, X1, X2, X3); + X0 ^= wKey[72]; X1 ^= wKey[73]; X2 ^= wKey[74]; X3 ^= wKey[75]; + inverseLT(); ib1(X0, X1, X2, X3); + X0 ^= wKey[68]; X1 ^= wKey[69]; X2 ^= wKey[70]; X3 ^= wKey[71]; + inverseLT(); ib0(X0, X1, X2, X3); + X0 ^= wKey[64]; X1 ^= wKey[65]; X2 ^= wKey[66]; X3 ^= wKey[67]; + inverseLT(); ib7(X0, X1, X2, X3); + X0 ^= wKey[60]; X1 ^= wKey[61]; X2 ^= wKey[62]; X3 ^= wKey[63]; + inverseLT(); ib6(X0, X1, X2, X3); + X0 ^= wKey[56]; X1 ^= wKey[57]; X2 ^= wKey[58]; X3 ^= wKey[59]; + inverseLT(); ib5(X0, X1, X2, X3); + X0 ^= wKey[52]; X1 ^= wKey[53]; X2 ^= wKey[54]; X3 ^= wKey[55]; + inverseLT(); ib4(X0, X1, X2, X3); + X0 ^= wKey[48]; X1 ^= wKey[49]; X2 ^= wKey[50]; X3 ^= wKey[51]; + inverseLT(); ib3(X0, X1, X2, X3); + X0 ^= wKey[44]; X1 ^= wKey[45]; X2 ^= wKey[46]; X3 ^= wKey[47]; + inverseLT(); ib2(X0, X1, X2, X3); + X0 ^= wKey[40]; X1 ^= wKey[41]; X2 ^= wKey[42]; X3 ^= wKey[43]; + inverseLT(); ib1(X0, X1, X2, X3); + X0 ^= wKey[36]; X1 ^= wKey[37]; X2 ^= wKey[38]; X3 ^= wKey[39]; + inverseLT(); ib0(X0, X1, X2, X3); + X0 ^= wKey[32]; X1 ^= wKey[33]; X2 ^= wKey[34]; X3 ^= wKey[35]; + inverseLT(); ib7(X0, X1, X2, X3); + X0 ^= wKey[28]; X1 ^= wKey[29]; X2 ^= wKey[30]; X3 ^= wKey[31]; + inverseLT(); ib6(X0, X1, X2, X3); + X0 ^= wKey[24]; X1 ^= wKey[25]; X2 ^= wKey[26]; X3 ^= wKey[27]; + inverseLT(); ib5(X0, X1, X2, X3); + X0 ^= wKey[20]; X1 ^= wKey[21]; X2 ^= wKey[22]; X3 ^= wKey[23]; + inverseLT(); ib4(X0, X1, X2, X3); + X0 ^= wKey[16]; X1 ^= wKey[17]; X2 ^= wKey[18]; X3 ^= wKey[19]; + inverseLT(); ib3(X0, X1, X2, X3); + X0 ^= wKey[12]; X1 ^= wKey[13]; X2 ^= wKey[14]; X3 ^= wKey[15]; + inverseLT(); ib2(X0, X1, X2, X3); + X0 ^= wKey[8]; X1 ^= wKey[9]; X2 ^= wKey[10]; X3 ^= wKey[11]; + inverseLT(); ib1(X0, X1, X2, X3); + X0 ^= wKey[4]; X1 ^= wKey[5]; X2 ^= wKey[6]; X3 ^= wKey[7]; + inverseLT(); ib0(X0, X1, X2, X3); + + wordToBytes(X3 ^ wKey[3], out, outOff); + wordToBytes(X2 ^ wKey[2], out, outOff + 4); + wordToBytes(X1 ^ wKey[1], out, outOff + 8); + wordToBytes(X0 ^ wKey[0], out, outOff + 12); + } + + /** + * The sboxes below are based on the work of Brian Gladman and + * Sam Simpson, whose original notice appears below. + *

+ * For further details see: + * http://fp.gladman.plus.com/cryptography_technology/serpent/ + */ + + /* Partially optimised Serpent S Box boolean functions derived */ + /* using a recursive descent analyser but without a full search */ + /* of all subtrees. This set of S boxes is the result of work */ + /* by Sam Simpson and Brian Gladman using the spare time on a */ + /* cluster of high capacity servers to search for S boxes with */ + /* this customised search engine. There are now an average of */ + /* 15.375 terms per S box. */ + /* */ + /* Copyright: Dr B. R Gladman (gladman@seven77.demon.co.uk) */ + /* and Sam Simpson (s.simpson@mia.co.uk) */ + /* 17th December 1998 */ + /* */ + /* We hereby give permission for information in this file to be */ + /* used freely subject only to acknowledgement of its origin. */ + + /** + * S0 - { 3, 8,15, 1,10, 6, 5,11,14,13, 4, 2, 7, 0, 9,12 } - 15 terms. + */ + private void sb0(int a, int b, int c, int d) + { + int t1 = a ^ d; + int t3 = c ^ t1; + int t4 = b ^ t3; + X3 = (a & d) ^ t4; + int t7 = a ^ (b & t1); + X2 = t4 ^ (c | t7); + int t12 = X3 & (t3 ^ t7); + X1 = (~t3) ^ t12; + X0 = t12 ^ (~t7); + } + + /** + * InvSO - {13, 3,11, 0,10, 6, 5,12, 1,14, 4, 7,15, 9, 8, 2 } - 15 terms. + */ + private void ib0(int a, int b, int c, int d) + { + int t1 = ~a; + int t2 = a ^ b; + int t4 = d ^ (t1 | t2); + int t5 = c ^ t4; + X2 = t2 ^ t5; + int t8 = t1 ^ (d & t2); + X1 = t4 ^ (X2 & t8); + X3 = (a & t4) ^ (t5 | X1); + X0 = X3 ^ (t5 ^ t8); + } + + /** + * S1 - {15,12, 2, 7, 9, 0, 5,10, 1,11,14, 8, 6,13, 3, 4 } - 14 terms. + */ + private void sb1(int a, int b, int c, int d) + { + int t2 = b ^ (~a); + int t5 = c ^ (a | t2); + X2 = d ^ t5; + int t7 = b ^ (d | t2); + int t8 = t2 ^ X2; + X3 = t8 ^ (t5 & t7); + int t11 = t5 ^ t7; + X1 = X3 ^ t11; + X0 = t5 ^ (t8 & t11); + } + + /** + * InvS1 - { 5, 8, 2,14,15, 6,12, 3,11, 4, 7, 9, 1,13,10, 0 } - 14 steps. + */ + private void ib1(int a, int b, int c, int d) + { + int t1 = b ^ d; + int t3 = a ^ (b & t1); + int t4 = t1 ^ t3; + X3 = c ^ t4; + int t7 = b ^ (t1 & t3); + int t8 = X3 | t7; + X1 = t3 ^ t8; + int t10 = ~X1; + int t11 = X3 ^ t7; + X0 = t10 ^ t11; + X2 = t4 ^ (t10 | t11); + } + + /** + * S2 - { 8, 6, 7, 9, 3,12,10,15,13, 1,14, 4, 0,11, 5, 2 } - 16 terms. + */ + private void sb2(int a, int b, int c, int d) + { + int t1 = ~a; + int t2 = b ^ d; + int t3 = c & t1; + X0 = t2 ^ t3; + int t5 = c ^ t1; + int t6 = c ^ X0; + int t7 = b & t6; + X3 = t5 ^ t7; + X2 = a ^ ((d | t7) & (X0 | t5)); + X1 = (t2 ^ X3) ^ (X2 ^ (d | t1)); + } + + /** + * InvS2 - {12, 9,15, 4,11,14, 1, 2, 0, 3, 6,13, 5, 8,10, 7 } - 16 steps. + */ + private void ib2(int a, int b, int c, int d) + { + int t1 = b ^ d; + int t2 = ~t1; + int t3 = a ^ c; + int t4 = c ^ t1; + int t5 = b & t4; + X0 = t3 ^ t5; + int t7 = a | t2; + int t8 = d ^ t7; + int t9 = t3 | t8; + X3 = t1 ^ t9; + int t11 = ~t4; + int t12 = X0 | X3; + X1 = t11 ^ t12; + X2 = (d & t11) ^ (t3 ^ t12); + } + + /** + * S3 - { 0,15,11, 8,12, 9, 6, 3,13, 1, 2, 4,10, 7, 5,14 } - 16 terms. + */ + private void sb3(int a, int b, int c, int d) + { + int t1 = a ^ b; + int t2 = a & c; + int t3 = a | d; + int t4 = c ^ d; + int t5 = t1 & t3; + int t6 = t2 | t5; + X2 = t4 ^ t6; + int t8 = b ^ t3; + int t9 = t6 ^ t8; + int t10 = t4 & t9; + X0 = t1 ^ t10; + int t12 = X2 & X0; + X1 = t9 ^ t12; + X3 = (b | d) ^ (t4 ^ t12); + } + + /** + * InvS3 - { 0, 9,10, 7,11,14, 6,13, 3, 5,12, 2, 4, 8,15, 1 } - 15 terms + */ + private void ib3(int a, int b, int c, int d) + { + int t1 = a | b; + int t2 = b ^ c; + int t3 = b & t2; + int t4 = a ^ t3; + int t5 = c ^ t4; + int t6 = d | t4; + X0 = t2 ^ t6; + int t8 = t2 | t6; + int t9 = d ^ t8; + X2 = t5 ^ t9; + int t11 = t1 ^ t9; + int t12 = X0 & t11; + X3 = t4 ^ t12; + X1 = X3 ^ (X0 ^ t11); + } + + /** + * S4 - { 1,15, 8, 3,12, 0,11, 6, 2, 5, 4,10, 9,14, 7,13 } - 15 terms. + */ + private void sb4(int a, int b, int c, int d) + { + int t1 = a ^ d; + int t2 = d & t1; + int t3 = c ^ t2; + int t4 = b | t3; + X3 = t1 ^ t4; + int t6 = ~b; + int t7 = t1 | t6; + X0 = t3 ^ t7; + int t9 = a & X0; + int t10 = t1 ^ t6; + int t11 = t4 & t10; + X2 = t9 ^ t11; + X1 = (a ^ t3) ^ (t10 & X2); + } + + /** + * InvS4 - { 5, 0, 8, 3,10, 9, 7,14, 2,12,11, 6, 4,15,13, 1 } - 15 terms. + */ + private void ib4(int a, int b, int c, int d) + { + int t1 = c | d; + int t2 = a & t1; + int t3 = b ^ t2; + int t4 = a & t3; + int t5 = c ^ t4; + X1 = d ^ t5; + int t7 = ~a; + int t8 = t5 & X1; + X3 = t3 ^ t8; + int t10 = X1 | t7; + int t11 = d ^ t10; + X0 = X3 ^ t11; + X2 = (t3 & t11) ^ (X1 ^ t7); + } + + /** + * S5 - {15, 5, 2,11, 4,10, 9,12, 0, 3,14, 8,13, 6, 7, 1 } - 16 terms. + */ + private void sb5(int a, int b, int c, int d) + { + int t1 = ~a; + int t2 = a ^ b; + int t3 = a ^ d; + int t4 = c ^ t1; + int t5 = t2 | t3; + X0 = t4 ^ t5; + int t7 = d & X0; + int t8 = t2 ^ X0; + X1 = t7 ^ t8; + int t10 = t1 | X0; + int t11 = t2 | t7; + int t12 = t3 ^ t10; + X2 = t11 ^ t12; + X3 = (b ^ t7) ^ (X1 & t12); + } + + /** + * InvS5 - { 8,15, 2, 9, 4, 1,13,14,11, 6, 5, 3, 7,12,10, 0 } - 16 terms. + */ + private void ib5(int a, int b, int c, int d) + { + int t1 = ~c; + int t2 = b & t1; + int t3 = d ^ t2; + int t4 = a & t3; + int t5 = b ^ t1; + X3 = t4 ^ t5; + int t7 = b | X3; + int t8 = a & t7; + X1 = t3 ^ t8; + int t10 = a | d; + int t11 = t1 ^ t7; + X0 = t10 ^ t11; + X2 = (b & t10) ^ (t4 | (a ^ c)); + } + + /** + * S6 - { 7, 2,12, 5, 8, 4, 6,11,14, 9, 1,15,13, 3,10, 0 } - 15 terms. + */ + private void sb6(int a, int b, int c, int d) + { + int t1 = ~a; + int t2 = a ^ d; + int t3 = b ^ t2; + int t4 = t1 | t2; + int t5 = c ^ t4; + X1 = b ^ t5; + int t7 = t2 | X1; + int t8 = d ^ t7; + int t9 = t5 & t8; + X2 = t3 ^ t9; + int t11 = t5 ^ t8; + X0 = X2 ^ t11; + X3 = (~t5) ^ (t3 & t11); + } + + /** + * InvS6 - {15,10, 1,13, 5, 3, 6, 0, 4, 9,14, 7, 2,12, 8,11 } - 15 terms. + */ + private void ib6(int a, int b, int c, int d) + { + int t1 = ~a; + int t2 = a ^ b; + int t3 = c ^ t2; + int t4 = c | t1; + int t5 = d ^ t4; + X1 = t3 ^ t5; + int t7 = t3 & t5; + int t8 = t2 ^ t7; + int t9 = b | t8; + X3 = t5 ^ t9; + int t11 = b | X3; + X0 = t8 ^ t11; + X2 = (d & t1) ^ (t3 ^ t11); + } + + /** + * S7 - { 1,13,15, 0,14, 8, 2,11, 7, 4,12,10, 9, 3, 5, 6 } - 16 terms. + */ + private void sb7(int a, int b, int c, int d) + { + int t1 = b ^ c; + int t2 = c & t1; + int t3 = d ^ t2; + int t4 = a ^ t3; + int t5 = d | t1; + int t6 = t4 & t5; + X1 = b ^ t6; + int t8 = t3 | X1; + int t9 = a & t4; + X3 = t1 ^ t9; + int t11 = t4 ^ t8; + int t12 = X3 & t11; + X2 = t3 ^ t12; + X0 = (~t11) ^ (X3 & X2); + } + + /** + * InvS7 - { 3, 0, 6,13, 9,14,15, 8, 5,12,11, 7,10, 1, 4, 2 } - 17 terms. + */ + private void ib7(int a, int b, int c, int d) + { + int t3 = c | (a & b); + int t4 = d & (a | b); + X3 = t3 ^ t4; + int t6 = ~d; + int t7 = b ^ t4; + int t9 = t7 | (X3 ^ t6); + X1 = a ^ t9; + X0 = (c ^ t7) ^ (d | X1); + X2 = (t3 ^ X1) ^ (X0 ^ (a & X3)); + } + + /** + * Apply the linear transformation to the register set. + */ + private void LT() + { + int x0 = rotateLeft(X0, 13); + int x2 = rotateLeft(X2, 3); + int x1 = X1 ^ x0 ^ x2 ; + int x3 = X3 ^ x2 ^ x0 << 3; + + X1 = rotateLeft(x1, 1); + X3 = rotateLeft(x3, 7); + X0 = rotateLeft(x0 ^ X1 ^ X3, 5); + X2 = rotateLeft(x2 ^ X3 ^ (X1 << 7), 22); + } + + /** + * Apply the inverse of the linear transformation to the register set. + */ + private void inverseLT() + { + int x2 = rotateRight(X2, 22) ^ X3 ^ (X1 << 7); + int x0 = rotateRight(X0, 5) ^ X1 ^ X3; + int x3 = rotateRight(X3, 7); + int x1 = rotateRight(X1, 1); + X3 = x3 ^ x2 ^ x0 << 3; + X1 = x1 ^ x0 ^ x2; + X2 = rotateRight(x2, 3); + X0 = rotateRight(x0, 13); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/SkipjackEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/SkipjackEngine.java new file mode 100644 index 000000000..0bd01506e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/SkipjackEngine.java @@ -0,0 +1,259 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * a class that provides a basic SKIPJACK engine. + */ +public class SkipjackEngine + implements BlockCipher +{ + static final int BLOCK_SIZE = 8; + + static short ftable[] = + { + 0xa3, 0xd7, 0x09, 0x83, 0xf8, 0x48, 0xf6, 0xf4, 0xb3, 0x21, 0x15, 0x78, 0x99, 0xb1, 0xaf, 0xf9, + 0xe7, 0x2d, 0x4d, 0x8a, 0xce, 0x4c, 0xca, 0x2e, 0x52, 0x95, 0xd9, 0x1e, 0x4e, 0x38, 0x44, 0x28, + 0x0a, 0xdf, 0x02, 0xa0, 0x17, 0xf1, 0x60, 0x68, 0x12, 0xb7, 0x7a, 0xc3, 0xe9, 0xfa, 0x3d, 0x53, + 0x96, 0x84, 0x6b, 0xba, 0xf2, 0x63, 0x9a, 0x19, 0x7c, 0xae, 0xe5, 0xf5, 0xf7, 0x16, 0x6a, 0xa2, + 0x39, 0xb6, 0x7b, 0x0f, 0xc1, 0x93, 0x81, 0x1b, 0xee, 0xb4, 0x1a, 0xea, 0xd0, 0x91, 0x2f, 0xb8, + 0x55, 0xb9, 0xda, 0x85, 0x3f, 0x41, 0xbf, 0xe0, 0x5a, 0x58, 0x80, 0x5f, 0x66, 0x0b, 0xd8, 0x90, + 0x35, 0xd5, 0xc0, 0xa7, 0x33, 0x06, 0x65, 0x69, 0x45, 0x00, 0x94, 0x56, 0x6d, 0x98, 0x9b, 0x76, + 0x97, 0xfc, 0xb2, 0xc2, 0xb0, 0xfe, 0xdb, 0x20, 0xe1, 0xeb, 0xd6, 0xe4, 0xdd, 0x47, 0x4a, 0x1d, + 0x42, 0xed, 0x9e, 0x6e, 0x49, 0x3c, 0xcd, 0x43, 0x27, 0xd2, 0x07, 0xd4, 0xde, 0xc7, 0x67, 0x18, + 0x89, 0xcb, 0x30, 0x1f, 0x8d, 0xc6, 0x8f, 0xaa, 0xc8, 0x74, 0xdc, 0xc9, 0x5d, 0x5c, 0x31, 0xa4, + 0x70, 0x88, 0x61, 0x2c, 0x9f, 0x0d, 0x2b, 0x87, 0x50, 0x82, 0x54, 0x64, 0x26, 0x7d, 0x03, 0x40, + 0x34, 0x4b, 0x1c, 0x73, 0xd1, 0xc4, 0xfd, 0x3b, 0xcc, 0xfb, 0x7f, 0xab, 0xe6, 0x3e, 0x5b, 0xa5, + 0xad, 0x04, 0x23, 0x9c, 0x14, 0x51, 0x22, 0xf0, 0x29, 0x79, 0x71, 0x7e, 0xff, 0x8c, 0x0e, 0xe2, + 0x0c, 0xef, 0xbc, 0x72, 0x75, 0x6f, 0x37, 0xa1, 0xec, 0xd3, 0x8e, 0x62, 0x8b, 0x86, 0x10, 0xe8, + 0x08, 0x77, 0x11, 0xbe, 0x92, 0x4f, 0x24, 0xc5, 0x32, 0x36, 0x9d, 0xcf, 0xf3, 0xa6, 0xbb, 0xac, + 0x5e, 0x6c, 0xa9, 0x13, 0x57, 0x25, 0xb5, 0xe3, 0xbd, 0xa8, 0x3a, 0x01, 0x05, 0x59, 0x2a, 0x46 + }; + + private int[] key0, key1, key2, key3; + private boolean encrypting; + + /** + * initialise a SKIPJACK cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("invalid parameter passed to SKIPJACK init - " + params.getClass().getName()); + } + + byte[] keyBytes = ((KeyParameter)params).getKey(); + + this.encrypting = encrypting; + this.key0 = new int[32]; + this.key1 = new int[32]; + this.key2 = new int[32]; + this.key3 = new int[32]; + + // + // expand the key to 128 bytes in 4 parts (saving us a modulo, multiply + // and an addition). + // + for (int i = 0; i < 32; i ++) + { + key0[i] = keyBytes[(i * 4) % 10] & 0xff; + key1[i] = keyBytes[(i * 4 + 1) % 10] & 0xff; + key2[i] = keyBytes[(i * 4 + 2) % 10] & 0xff; + key3[i] = keyBytes[(i * 4 + 3) % 10] & 0xff; + } + } + + public String getAlgorithmName() + { + return "SKIPJACK"; + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (key1 == null) + { + throw new IllegalStateException("SKIPJACK engine not initialised"); + } + + if ((inOff + BLOCK_SIZE) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + BLOCK_SIZE) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (encrypting) + { + encryptBlock(in, inOff, out, outOff); + } + else + { + decryptBlock(in, inOff, out, outOff); + } + + return BLOCK_SIZE; + } + + public void reset() + { + } + + /** + * The G permutation + */ + private int g( + int k, + int w) + { + int g1, g2, g3, g4, g5, g6; + + g1 = (w >> 8) & 0xff; + g2 = w & 0xff; + + g3 = ftable[g2 ^ key0[k]] ^ g1; + g4 = ftable[g3 ^ key1[k]] ^ g2; + g5 = ftable[g4 ^ key2[k]] ^ g3; + g6 = ftable[g5 ^ key3[k]] ^ g4; + + return ((g5 << 8) + g6); + } + + public int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int w1 = (in[inOff + 0] << 8) + (in[inOff + 1] & 0xff); + int w2 = (in[inOff + 2] << 8) + (in[inOff + 3] & 0xff); + int w3 = (in[inOff + 4] << 8) + (in[inOff + 5] & 0xff); + int w4 = (in[inOff + 6] << 8) + (in[inOff + 7] & 0xff); + + int k = 0; + + for (int t = 0; t < 2; t++) + { + for(int i = 0; i < 8; i++) + { + int tmp = w4; + w4 = w3; + w3 = w2; + w2 = g(k, w1); + w1 = w2 ^ tmp ^ (k + 1); + k++; + } + + for(int i = 0; i < 8; i++) + { + int tmp = w4; + w4 = w3; + w3 = w1 ^ w2 ^ (k + 1); + w2 = g(k, w1); + w1 = tmp; + k++; + } + } + + out[outOff + 0] = (byte)((w1 >> 8)); + out[outOff + 1] = (byte)(w1); + out[outOff + 2] = (byte)((w2 >> 8)); + out[outOff + 3] = (byte)(w2); + out[outOff + 4] = (byte)((w3 >> 8)); + out[outOff + 5] = (byte)(w3); + out[outOff + 6] = (byte)((w4 >> 8)); + out[outOff + 7] = (byte)(w4); + + return BLOCK_SIZE; + } + + /** + * the inverse of the G permutation. + */ + private int h( + int k, + int w) + { + int h1, h2, h3, h4, h5, h6; + + h1 = w & 0xff; + h2 = (w >> 8) & 0xff; + + h3 = ftable[h2 ^ key3[k]] ^ h1; + h4 = ftable[h3 ^ key2[k]] ^ h2; + h5 = ftable[h4 ^ key1[k]] ^ h3; + h6 = ftable[h5 ^ key0[k]] ^ h4; + + return ((h6 << 8) + h5); + } + + public int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int w2 = (in[inOff + 0] << 8) + (in[inOff + 1] & 0xff); + int w1 = (in[inOff + 2] << 8) + (in[inOff + 3] & 0xff); + int w4 = (in[inOff + 4] << 8) + (in[inOff + 5] & 0xff); + int w3 = (in[inOff + 6] << 8) + (in[inOff + 7] & 0xff); + + int k = 31; + + for (int t = 0; t < 2; t++) + { + for(int i = 0; i < 8; i++) + { + int tmp = w4; + w4 = w3; + w3 = w2; + w2 = h(k, w1); + w1 = w2 ^ tmp ^ (k + 1); + k--; + } + + for(int i = 0; i < 8; i++) + { + int tmp = w4; + w4 = w3; + w3 = w1 ^ w2 ^ (k + 1); + w2 = h(k, w1); + w1 = tmp; + k--; + } + } + + out[outOff + 0] = (byte)((w2 >> 8)); + out[outOff + 1] = (byte)(w2); + out[outOff + 2] = (byte)((w1 >> 8)); + out[outOff + 3] = (byte)(w1); + out[outOff + 4] = (byte)((w4 >> 8)); + out[outOff + 5] = (byte)(w4); + out[outOff + 6] = (byte)((w3 >> 8)); + out[outOff + 7] = (byte)(w3); + + return BLOCK_SIZE; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/TEAEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/TEAEngine.java new file mode 100644 index 000000000..691ca8a34 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/TEAEngine.java @@ -0,0 +1,178 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * An TEA engine. + */ +public class TEAEngine + implements BlockCipher +{ + private static final int rounds = 32, + block_size = 8, +// key_size = 16, + delta = 0x9E3779B9, + d_sum = 0xC6EF3720; // sum on decrypt + /* + * the expanded key array of 4 subkeys + */ + private int _a, _b, _c, _d; + private boolean _initialised; + private boolean _forEncryption; + + /** + * Create an instance of the TEA encryption algorithm + * and set some defaults + */ + public TEAEngine() + { + _initialised = false; + } + + public String getAlgorithmName() + { + return "TEA"; + } + + public int getBlockSize() + { + return block_size; + } + + /** + * initialise + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("invalid parameter passed to TEA init - " + params.getClass().getName()); + } + + _forEncryption = forEncryption; + _initialised = true; + + KeyParameter p = (KeyParameter)params; + + setKey(p.getKey()); + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (!_initialised) + { + throw new IllegalStateException(getAlgorithmName()+" not initialised"); + } + + if ((inOff + block_size) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + block_size) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + return (_forEncryption) ? encryptBlock(in, inOff, out, outOff) + : decryptBlock(in, inOff, out, outOff); + } + + public void reset() + { + } + + /** + * Re-key the cipher. + *

+ * @param key the key to be used + */ + private void setKey( + byte[] key) + { + _a = bytesToInt(key, 0); + _b = bytesToInt(key, 4); + _c = bytesToInt(key, 8); + _d = bytesToInt(key, 12); + } + + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + // Pack bytes into integers + int v0 = bytesToInt(in, inOff); + int v1 = bytesToInt(in, inOff + 4); + + int sum = 0; + + for (int i = 0; i != rounds; i++) + { + sum += delta; + v0 += ((v1 << 4) + _a) ^ (v1 + sum) ^ ((v1 >>> 5) + _b); + v1 += ((v0 << 4) + _c) ^ (v0 + sum) ^ ((v0 >>> 5) + _d); + } + + unpackInt(v0, out, outOff); + unpackInt(v1, out, outOff + 4); + + return block_size; + } + + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + // Pack bytes into integers + int v0 = bytesToInt(in, inOff); + int v1 = bytesToInt(in, inOff + 4); + + int sum = d_sum; + + for (int i = 0; i != rounds; i++) + { + v1 -= ((v0 << 4) + _c) ^ (v0 + sum) ^ ((v0 >>> 5) + _d); + v0 -= ((v1 << 4) + _a) ^ (v1 + sum) ^ ((v1 >>> 5) + _b); + sum -= delta; + } + + unpackInt(v0, out, outOff); + unpackInt(v1, out, outOff + 4); + + return block_size; + } + + private int bytesToInt(byte[] in, int inOff) + { + return ((in[inOff++]) << 24) | + ((in[inOff++] & 255) << 16) | + ((in[inOff++] & 255) << 8) | + ((in[inOff] & 255)); + } + + private void unpackInt(int v, byte[] out, int outOff) + { + out[outOff++] = (byte)(v >>> 24); + out[outOff++] = (byte)(v >>> 16); + out[outOff++] = (byte)(v >>> 8); + out[outOff ] = (byte)v; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/TwofishEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/TwofishEngine.java new file mode 100644 index 000000000..d7b031391 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/TwofishEngine.java @@ -0,0 +1,677 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * A class that provides Twofish encryption operations. + * + * This Java implementation is based on the Java reference + * implementation provided by Bruce Schneier and developed + * by Raif S. Naffah. + */ +public final class TwofishEngine + implements BlockCipher +{ + private static final byte[][] P = { + { // p0 + (byte) 0xA9, (byte) 0x67, (byte) 0xB3, (byte) 0xE8, + (byte) 0x04, (byte) 0xFD, (byte) 0xA3, (byte) 0x76, + (byte) 0x9A, (byte) 0x92, (byte) 0x80, (byte) 0x78, + (byte) 0xE4, (byte) 0xDD, (byte) 0xD1, (byte) 0x38, + (byte) 0x0D, (byte) 0xC6, (byte) 0x35, (byte) 0x98, + (byte) 0x18, (byte) 0xF7, (byte) 0xEC, (byte) 0x6C, + (byte) 0x43, (byte) 0x75, (byte) 0x37, (byte) 0x26, + (byte) 0xFA, (byte) 0x13, (byte) 0x94, (byte) 0x48, + (byte) 0xF2, (byte) 0xD0, (byte) 0x8B, (byte) 0x30, + (byte) 0x84, (byte) 0x54, (byte) 0xDF, (byte) 0x23, + (byte) 0x19, (byte) 0x5B, (byte) 0x3D, (byte) 0x59, + (byte) 0xF3, (byte) 0xAE, (byte) 0xA2, (byte) 0x82, + (byte) 0x63, (byte) 0x01, (byte) 0x83, (byte) 0x2E, + (byte) 0xD9, (byte) 0x51, (byte) 0x9B, (byte) 0x7C, + (byte) 0xA6, (byte) 0xEB, (byte) 0xA5, (byte) 0xBE, + (byte) 0x16, (byte) 0x0C, (byte) 0xE3, (byte) 0x61, + (byte) 0xC0, (byte) 0x8C, (byte) 0x3A, (byte) 0xF5, + (byte) 0x73, (byte) 0x2C, (byte) 0x25, (byte) 0x0B, + (byte) 0xBB, (byte) 0x4E, (byte) 0x89, (byte) 0x6B, + (byte) 0x53, (byte) 0x6A, (byte) 0xB4, (byte) 0xF1, + (byte) 0xE1, (byte) 0xE6, (byte) 0xBD, (byte) 0x45, + (byte) 0xE2, (byte) 0xF4, (byte) 0xB6, (byte) 0x66, + (byte) 0xCC, (byte) 0x95, (byte) 0x03, (byte) 0x56, + (byte) 0xD4, (byte) 0x1C, (byte) 0x1E, (byte) 0xD7, + (byte) 0xFB, (byte) 0xC3, (byte) 0x8E, (byte) 0xB5, + (byte) 0xE9, (byte) 0xCF, (byte) 0xBF, (byte) 0xBA, + (byte) 0xEA, (byte) 0x77, (byte) 0x39, (byte) 0xAF, + (byte) 0x33, (byte) 0xC9, (byte) 0x62, (byte) 0x71, + (byte) 0x81, (byte) 0x79, (byte) 0x09, (byte) 0xAD, + (byte) 0x24, (byte) 0xCD, (byte) 0xF9, (byte) 0xD8, + (byte) 0xE5, (byte) 0xC5, (byte) 0xB9, (byte) 0x4D, + (byte) 0x44, (byte) 0x08, (byte) 0x86, (byte) 0xE7, + (byte) 0xA1, (byte) 0x1D, (byte) 0xAA, (byte) 0xED, + (byte) 0x06, (byte) 0x70, (byte) 0xB2, (byte) 0xD2, + (byte) 0x41, (byte) 0x7B, (byte) 0xA0, (byte) 0x11, + (byte) 0x31, (byte) 0xC2, (byte) 0x27, (byte) 0x90, + (byte) 0x20, (byte) 0xF6, (byte) 0x60, (byte) 0xFF, + (byte) 0x96, (byte) 0x5C, (byte) 0xB1, (byte) 0xAB, + (byte) 0x9E, (byte) 0x9C, (byte) 0x52, (byte) 0x1B, + (byte) 0x5F, (byte) 0x93, (byte) 0x0A, (byte) 0xEF, + (byte) 0x91, (byte) 0x85, (byte) 0x49, (byte) 0xEE, + (byte) 0x2D, (byte) 0x4F, (byte) 0x8F, (byte) 0x3B, + (byte) 0x47, (byte) 0x87, (byte) 0x6D, (byte) 0x46, + (byte) 0xD6, (byte) 0x3E, (byte) 0x69, (byte) 0x64, + (byte) 0x2A, (byte) 0xCE, (byte) 0xCB, (byte) 0x2F, + (byte) 0xFC, (byte) 0x97, (byte) 0x05, (byte) 0x7A, + (byte) 0xAC, (byte) 0x7F, (byte) 0xD5, (byte) 0x1A, + (byte) 0x4B, (byte) 0x0E, (byte) 0xA7, (byte) 0x5A, + (byte) 0x28, (byte) 0x14, (byte) 0x3F, (byte) 0x29, + (byte) 0x88, (byte) 0x3C, (byte) 0x4C, (byte) 0x02, + (byte) 0xB8, (byte) 0xDA, (byte) 0xB0, (byte) 0x17, + (byte) 0x55, (byte) 0x1F, (byte) 0x8A, (byte) 0x7D, + (byte) 0x57, (byte) 0xC7, (byte) 0x8D, (byte) 0x74, + (byte) 0xB7, (byte) 0xC4, (byte) 0x9F, (byte) 0x72, + (byte) 0x7E, (byte) 0x15, (byte) 0x22, (byte) 0x12, + (byte) 0x58, (byte) 0x07, (byte) 0x99, (byte) 0x34, + (byte) 0x6E, (byte) 0x50, (byte) 0xDE, (byte) 0x68, + (byte) 0x65, (byte) 0xBC, (byte) 0xDB, (byte) 0xF8, + (byte) 0xC8, (byte) 0xA8, (byte) 0x2B, (byte) 0x40, + (byte) 0xDC, (byte) 0xFE, (byte) 0x32, (byte) 0xA4, + (byte) 0xCA, (byte) 0x10, (byte) 0x21, (byte) 0xF0, + (byte) 0xD3, (byte) 0x5D, (byte) 0x0F, (byte) 0x00, + (byte) 0x6F, (byte) 0x9D, (byte) 0x36, (byte) 0x42, + (byte) 0x4A, (byte) 0x5E, (byte) 0xC1, (byte) 0xE0 }, + { // p1 + (byte) 0x75, (byte) 0xF3, (byte) 0xC6, (byte) 0xF4, + (byte) 0xDB, (byte) 0x7B, (byte) 0xFB, (byte) 0xC8, + (byte) 0x4A, (byte) 0xD3, (byte) 0xE6, (byte) 0x6B, + (byte) 0x45, (byte) 0x7D, (byte) 0xE8, (byte) 0x4B, + (byte) 0xD6, (byte) 0x32, (byte) 0xD8, (byte) 0xFD, + (byte) 0x37, (byte) 0x71, (byte) 0xF1, (byte) 0xE1, + (byte) 0x30, (byte) 0x0F, (byte) 0xF8, (byte) 0x1B, + (byte) 0x87, (byte) 0xFA, (byte) 0x06, (byte) 0x3F, + (byte) 0x5E, (byte) 0xBA, (byte) 0xAE, (byte) 0x5B, + (byte) 0x8A, (byte) 0x00, (byte) 0xBC, (byte) 0x9D, + (byte) 0x6D, (byte) 0xC1, (byte) 0xB1, (byte) 0x0E, + (byte) 0x80, (byte) 0x5D, (byte) 0xD2, (byte) 0xD5, + (byte) 0xA0, (byte) 0x84, (byte) 0x07, (byte) 0x14, + (byte) 0xB5, (byte) 0x90, (byte) 0x2C, (byte) 0xA3, + (byte) 0xB2, (byte) 0x73, (byte) 0x4C, (byte) 0x54, + (byte) 0x92, (byte) 0x74, (byte) 0x36, (byte) 0x51, + (byte) 0x38, (byte) 0xB0, (byte) 0xBD, (byte) 0x5A, + (byte) 0xFC, (byte) 0x60, (byte) 0x62, (byte) 0x96, + (byte) 0x6C, (byte) 0x42, (byte) 0xF7, (byte) 0x10, + (byte) 0x7C, (byte) 0x28, (byte) 0x27, (byte) 0x8C, + (byte) 0x13, (byte) 0x95, (byte) 0x9C, (byte) 0xC7, + (byte) 0x24, (byte) 0x46, (byte) 0x3B, (byte) 0x70, + (byte) 0xCA, (byte) 0xE3, (byte) 0x85, (byte) 0xCB, + (byte) 0x11, (byte) 0xD0, (byte) 0x93, (byte) 0xB8, + (byte) 0xA6, (byte) 0x83, (byte) 0x20, (byte) 0xFF, + (byte) 0x9F, (byte) 0x77, (byte) 0xC3, (byte) 0xCC, + (byte) 0x03, (byte) 0x6F, (byte) 0x08, (byte) 0xBF, + (byte) 0x40, (byte) 0xE7, (byte) 0x2B, (byte) 0xE2, + (byte) 0x79, (byte) 0x0C, (byte) 0xAA, (byte) 0x82, + (byte) 0x41, (byte) 0x3A, (byte) 0xEA, (byte) 0xB9, + (byte) 0xE4, (byte) 0x9A, (byte) 0xA4, (byte) 0x97, + (byte) 0x7E, (byte) 0xDA, (byte) 0x7A, (byte) 0x17, + (byte) 0x66, (byte) 0x94, (byte) 0xA1, (byte) 0x1D, + (byte) 0x3D, (byte) 0xF0, (byte) 0xDE, (byte) 0xB3, + (byte) 0x0B, (byte) 0x72, (byte) 0xA7, (byte) 0x1C, + (byte) 0xEF, (byte) 0xD1, (byte) 0x53, (byte) 0x3E, + (byte) 0x8F, (byte) 0x33, (byte) 0x26, (byte) 0x5F, + (byte) 0xEC, (byte) 0x76, (byte) 0x2A, (byte) 0x49, + (byte) 0x81, (byte) 0x88, (byte) 0xEE, (byte) 0x21, + (byte) 0xC4, (byte) 0x1A, (byte) 0xEB, (byte) 0xD9, + (byte) 0xC5, (byte) 0x39, (byte) 0x99, (byte) 0xCD, + (byte) 0xAD, (byte) 0x31, (byte) 0x8B, (byte) 0x01, + (byte) 0x18, (byte) 0x23, (byte) 0xDD, (byte) 0x1F, + (byte) 0x4E, (byte) 0x2D, (byte) 0xF9, (byte) 0x48, + (byte) 0x4F, (byte) 0xF2, (byte) 0x65, (byte) 0x8E, + (byte) 0x78, (byte) 0x5C, (byte) 0x58, (byte) 0x19, + (byte) 0x8D, (byte) 0xE5, (byte) 0x98, (byte) 0x57, + (byte) 0x67, (byte) 0x7F, (byte) 0x05, (byte) 0x64, + (byte) 0xAF, (byte) 0x63, (byte) 0xB6, (byte) 0xFE, + (byte) 0xF5, (byte) 0xB7, (byte) 0x3C, (byte) 0xA5, + (byte) 0xCE, (byte) 0xE9, (byte) 0x68, (byte) 0x44, + (byte) 0xE0, (byte) 0x4D, (byte) 0x43, (byte) 0x69, + (byte) 0x29, (byte) 0x2E, (byte) 0xAC, (byte) 0x15, + (byte) 0x59, (byte) 0xA8, (byte) 0x0A, (byte) 0x9E, + (byte) 0x6E, (byte) 0x47, (byte) 0xDF, (byte) 0x34, + (byte) 0x35, (byte) 0x6A, (byte) 0xCF, (byte) 0xDC, + (byte) 0x22, (byte) 0xC9, (byte) 0xC0, (byte) 0x9B, + (byte) 0x89, (byte) 0xD4, (byte) 0xED, (byte) 0xAB, + (byte) 0x12, (byte) 0xA2, (byte) 0x0D, (byte) 0x52, + (byte) 0xBB, (byte) 0x02, (byte) 0x2F, (byte) 0xA9, + (byte) 0xD7, (byte) 0x61, (byte) 0x1E, (byte) 0xB4, + (byte) 0x50, (byte) 0x04, (byte) 0xF6, (byte) 0xC2, + (byte) 0x16, (byte) 0x25, (byte) 0x86, (byte) 0x56, + (byte) 0x55, (byte) 0x09, (byte) 0xBE, (byte) 0x91 } + }; + + /** + * Define the fixed p0/p1 permutations used in keyed S-box lookup. + * By changing the following constant definitions, the S-boxes will + * automatically get changed in the Twofish engine. + */ + private static final int P_00 = 1; + private static final int P_01 = 0; + private static final int P_02 = 0; + private static final int P_03 = P_01 ^ 1; + private static final int P_04 = 1; + + private static final int P_10 = 0; + private static final int P_11 = 0; + private static final int P_12 = 1; + private static final int P_13 = P_11 ^ 1; + private static final int P_14 = 0; + + private static final int P_20 = 1; + private static final int P_21 = 1; + private static final int P_22 = 0; + private static final int P_23 = P_21 ^ 1; + private static final int P_24 = 0; + + private static final int P_30 = 0; + private static final int P_31 = 1; + private static final int P_32 = 1; + private static final int P_33 = P_31 ^ 1; + private static final int P_34 = 1; + + /* Primitive polynomial for GF(256) */ + private static final int GF256_FDBK = 0x169; + private static final int GF256_FDBK_2 = GF256_FDBK / 2; + private static final int GF256_FDBK_4 = GF256_FDBK / 4; + + private static final int RS_GF_FDBK = 0x14D; // field generator + + //==================================== + // Useful constants + //==================================== + + private static final int ROUNDS = 16; + private static final int MAX_ROUNDS = 16; // bytes = 128 bits + private static final int BLOCK_SIZE = 16; // bytes = 128 bits + private static final int MAX_KEY_BITS = 256; + + private static final int INPUT_WHITEN=0; + private static final int OUTPUT_WHITEN=INPUT_WHITEN+BLOCK_SIZE/4; // 4 + private static final int ROUND_SUBKEYS=OUTPUT_WHITEN+BLOCK_SIZE/4;// 8 + + private static final int TOTAL_SUBKEYS=ROUND_SUBKEYS+2*MAX_ROUNDS;// 40 + + private static final int SK_STEP = 0x02020202; + private static final int SK_BUMP = 0x01010101; + private static final int SK_ROTL = 9; + + private boolean encrypting = false; + + private int[] gMDS0 = new int[MAX_KEY_BITS]; + private int[] gMDS1 = new int[MAX_KEY_BITS]; + private int[] gMDS2 = new int[MAX_KEY_BITS]; + private int[] gMDS3 = new int[MAX_KEY_BITS]; + + /** + * gSubKeys[] and gSBox[] are eventually used in the + * encryption and decryption methods. + */ + private int[] gSubKeys; + private int[] gSBox; + + private int k64Cnt = 0; + + private byte[] workingKey = null; + + public TwofishEngine() + { + // calculate the MDS matrix + int[] m1 = new int[2]; + int[] mX = new int[2]; + int[] mY = new int[2]; + int j; + + for (int i=0; i< MAX_KEY_BITS ; i++) + { + j = P[0][i] & 0xff; + m1[0] = j; + mX[0] = Mx_X(j) & 0xff; + mY[0] = Mx_Y(j) & 0xff; + + j = P[1][i] & 0xff; + m1[1] = j; + mX[1] = Mx_X(j) & 0xff; + mY[1] = Mx_Y(j) & 0xff; + + gMDS0[i] = m1[P_00] | mX[P_00] << 8 | + mY[P_00] << 16 | mY[P_00] << 24; + + gMDS1[i] = mY[P_10] | mY[P_10] << 8 | + mX[P_10] << 16 | m1[P_10] << 24; + + gMDS2[i] = mX[P_20] | mY[P_20] << 8 | + m1[P_20] << 16 | mY[P_20] << 24; + + gMDS3[i] = mX[P_30] | m1[P_30] << 8 | + mY[P_30] << 16 | mX[P_30] << 24; + } + } + + /** + * initialise a Twofish cipher. + * + * @param encrypting whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + { + if (params instanceof KeyParameter) + { + this.encrypting = encrypting; + this.workingKey = ((KeyParameter)params).getKey(); + this.k64Cnt = (this.workingKey.length / 8); // pre-padded ? + setKey(this.workingKey); + + return; + } + + throw new IllegalArgumentException("invalid parameter passed to Twofish init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "Twofish"; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (workingKey == null) + { + throw new IllegalStateException("Twofish not initialised"); + } + + if ((inOff + BLOCK_SIZE) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + BLOCK_SIZE) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (encrypting) + { + encryptBlock(in, inOff, out, outOff); + } + else + { + decryptBlock(in, inOff, out, outOff); + } + + return BLOCK_SIZE; + } + + public void reset() + { + if (this.workingKey != null) + { + setKey(this.workingKey); + } + } + + public int getBlockSize() + { + return BLOCK_SIZE; + } + + //================================== + // Private Implementation + //================================== + + private void setKey(byte[] key) + { + int[] k32e = new int[MAX_KEY_BITS/64]; // 4 + int[] k32o = new int[MAX_KEY_BITS/64]; // 4 + + int[] sBoxKeys = new int[MAX_KEY_BITS/64]; // 4 + gSubKeys = new int[TOTAL_SUBKEYS]; + + if (k64Cnt < 1) + { + throw new IllegalArgumentException("Key size less than 64 bits"); + } + + if (k64Cnt > 4) + { + throw new IllegalArgumentException("Key size larger than 256 bits"); + } + + /* + * k64Cnt is the number of 8 byte blocks (64 chunks) + * that are in the input key. The input key is a + * maximum of 32 bytes (256 bits), so the range + * for k64Cnt is 1..4 + */ + for (int i=0; i>> 24; + A += B; + gSubKeys[i*2] = A; + A += B; + gSubKeys[i*2 + 1] = A << SK_ROTL | A >>> (32-SK_ROTL); + } + + /* + * fully expand the table for speed + */ + int k0 = sBoxKeys[0]; + int k1 = sBoxKeys[1]; + int k2 = sBoxKeys[2]; + int k3 = sBoxKeys[3]; + int b0, b1, b2, b3; + gSBox = new int[4*MAX_KEY_BITS]; + for (int i=0; i>>1 | x2 << 31; + x3 = (x3 << 1 | x3 >>> 31) ^ (t0 + 2*t1 + gSubKeys[k++]); + + t0 = Fe32_0(x2); + t1 = Fe32_3(x3); + x0 ^= t0 + t1 + gSubKeys[k++]; + x0 = x0 >>>1 | x0 << 31; + x1 = (x1 << 1 | x1 >>> 31) ^ (t0 + 2*t1 + gSubKeys[k++]); + } + + Bits32ToBytes(x2 ^ gSubKeys[OUTPUT_WHITEN], dst, dstIndex); + Bits32ToBytes(x3 ^ gSubKeys[OUTPUT_WHITEN + 1], dst, dstIndex + 4); + Bits32ToBytes(x0 ^ gSubKeys[OUTPUT_WHITEN + 2], dst, dstIndex + 8); + Bits32ToBytes(x1 ^ gSubKeys[OUTPUT_WHITEN + 3], dst, dstIndex + 12); + } + + /** + * Decrypt the given input starting at the given offset and place + * the result in the provided buffer starting at the given offset. + * The input will be an exact multiple of our blocksize. + */ + private void decryptBlock( + byte[] src, + int srcIndex, + byte[] dst, + int dstIndex) + { + int x2 = BytesTo32Bits(src, srcIndex) ^ gSubKeys[OUTPUT_WHITEN]; + int x3 = BytesTo32Bits(src, srcIndex+4) ^ gSubKeys[OUTPUT_WHITEN + 1]; + int x0 = BytesTo32Bits(src, srcIndex+8) ^ gSubKeys[OUTPUT_WHITEN + 2]; + int x1 = BytesTo32Bits(src, srcIndex+12) ^ gSubKeys[OUTPUT_WHITEN + 3]; + + int k = ROUND_SUBKEYS + 2 * ROUNDS -1 ; + int t0, t1; + for (int r = 0; r< ROUNDS ; r +=2) + { + t0 = Fe32_0(x2); + t1 = Fe32_3(x3); + x1 ^= t0 + 2*t1 + gSubKeys[k--]; + x0 = (x0 << 1 | x0 >>> 31) ^ (t0 + t1 + gSubKeys[k--]); + x1 = x1 >>>1 | x1 << 31; + + t0 = Fe32_0(x0); + t1 = Fe32_3(x1); + x3 ^= t0 + 2*t1 + gSubKeys[k--]; + x2 = (x2 << 1 | x2 >>> 31) ^ (t0 + t1 + gSubKeys[k--]); + x3 = x3 >>>1 | x3 << 31; + } + + Bits32ToBytes(x0 ^ gSubKeys[INPUT_WHITEN], dst, dstIndex); + Bits32ToBytes(x1 ^ gSubKeys[INPUT_WHITEN + 1], dst, dstIndex + 4); + Bits32ToBytes(x2 ^ gSubKeys[INPUT_WHITEN + 2], dst, dstIndex + 8); + Bits32ToBytes(x3 ^ gSubKeys[INPUT_WHITEN + 3], dst, dstIndex + 12); + } + + /* + * TODO: This can be optimised and made cleaner by combining + * the functionality in this function and applying it appropriately + * to the creation of the subkeys during key setup. + */ + private int F32(int x, int[] k32) + { + int b0 = b0(x); + int b1 = b1(x); + int b2 = b2(x); + int b3 = b3(x); + int k0 = k32[0]; + int k1 = k32[1]; + int k2 = k32[2]; + int k3 = k32[3]; + + int result = 0; + switch (k64Cnt & 3) + { + case 1: + result = gMDS0[(P[P_01][b0] & 0xff) ^ b0(k0)] ^ + gMDS1[(P[P_11][b1] & 0xff) ^ b1(k0)] ^ + gMDS2[(P[P_21][b2] & 0xff) ^ b2(k0)] ^ + gMDS3[(P[P_31][b3] & 0xff) ^ b3(k0)]; + break; + case 0: /* 256 bits of key */ + b0 = (P[P_04][b0] & 0xff) ^ b0(k3); + b1 = (P[P_14][b1] & 0xff) ^ b1(k3); + b2 = (P[P_24][b2] & 0xff) ^ b2(k3); + b3 = (P[P_34][b3] & 0xff) ^ b3(k3); + case 3: + b0 = (P[P_03][b0] & 0xff) ^ b0(k2); + b1 = (P[P_13][b1] & 0xff) ^ b1(k2); + b2 = (P[P_23][b2] & 0xff) ^ b2(k2); + b3 = (P[P_33][b3] & 0xff) ^ b3(k2); + case 2: + result = + gMDS0[(P[P_01][(P[P_02][b0]&0xff)^b0(k1)]&0xff)^b0(k0)] ^ + gMDS1[(P[P_11][(P[P_12][b1]&0xff)^b1(k1)]&0xff)^b1(k0)] ^ + gMDS2[(P[P_21][(P[P_22][b2]&0xff)^b2(k1)]&0xff)^b2(k0)] ^ + gMDS3[(P[P_31][(P[P_32][b3]&0xff)^b3(k1)]&0xff)^b3(k0)]; + break; + } + return result; + } + + /** + * Use (12, 8) Reed-Solomon code over GF(256) to produce + * a key S-box 32-bit entity from 2 key material 32-bit + * entities. + * + * @param k0 first 32-bit entity + * @param k1 second 32-bit entity + * @return Remainder polynomial generated using RS code + */ + private int RS_MDS_Encode(int k0, int k1) + { + int r = k1; + for (int i = 0 ; i < 4 ; i++) // shift 1 byte at a time + { + r = RS_rem(r); + } + r ^= k0; + for (int i=0 ; i < 4 ; i++) + { + r = RS_rem(r); + } + + return r; + } + + /** + * Reed-Solomon code parameters: (12,8) reversible code:

+ *

+     * g(x) = x^4 + (a+1/a)x^3 + ax^2 + (a+1/a)x + 1
+     * 
+ * where a = primitive root of field generator 0x14D + */ + private int RS_rem(int x) + { + int b = (x >>> 24) & 0xff; + int g2 = ((b << 1) ^ + ((b & 0x80) != 0 ? RS_GF_FDBK : 0)) & 0xff; + int g3 = ((b >>> 1) ^ + ((b & 0x01) != 0 ? (RS_GF_FDBK >>> 1) : 0)) ^ g2 ; + return ((x << 8) ^ (g3 << 24) ^ (g2 << 16) ^ (g3 << 8) ^ b); + } + + private int LFSR1(int x) + { + return (x >> 1) ^ + (((x & 0x01) != 0) ? GF256_FDBK_2 : 0); + } + + private int LFSR2(int x) + { + return (x >> 2) ^ + (((x & 0x02) != 0) ? GF256_FDBK_2 : 0) ^ + (((x & 0x01) != 0) ? GF256_FDBK_4 : 0); + } + + private int Mx_X(int x) + { + return x ^ LFSR2(x); + } // 5B + + private int Mx_Y(int x) + { + return x ^ LFSR1(x) ^ LFSR2(x); + } // EF + + private int b0(int x) + { + return x & 0xff; + } + + private int b1(int x) + { + return (x >>> 8) & 0xff; + } + + private int b2(int x) + { + return (x >>> 16) & 0xff; + } + + private int b3(int x) + { + return (x >>> 24) & 0xff; + } + + private int Fe32_0(int x) + { + return gSBox[ 0x000 + 2*(x & 0xff) ] ^ + gSBox[ 0x001 + 2*((x >>> 8) & 0xff) ] ^ + gSBox[ 0x200 + 2*((x >>> 16) & 0xff) ] ^ + gSBox[ 0x201 + 2*((x >>> 24) & 0xff) ]; + } + + private int Fe32_3(int x) + { + return gSBox[ 0x000 + 2*((x >>> 24) & 0xff) ] ^ + gSBox[ 0x001 + 2*(x & 0xff) ] ^ + gSBox[ 0x200 + 2*((x >>> 8) & 0xff) ] ^ + gSBox[ 0x201 + 2*((x >>> 16) & 0xff) ]; + } + + private int BytesTo32Bits(byte[] b, int p) + { + return ((b[p] & 0xff)) | + ((b[p+1] & 0xff) << 8) | + ((b[p+2] & 0xff) << 16) | + ((b[p+3] & 0xff) << 24); + } + + private void Bits32ToBytes(int in, byte[] b, int offset) + { + b[offset] = (byte)in; + b[offset + 1] = (byte)(in >> 8); + b[offset + 2] = (byte)(in >> 16); + b[offset + 3] = (byte)(in >> 24); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/VMPCEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/VMPCEngine.java new file mode 100644 index 000000000..9780d3ad4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/VMPCEngine.java @@ -0,0 +1,138 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.StreamCipher; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +public class VMPCEngine implements StreamCipher +{ + /* + * variables to hold the state of the VMPC engine during encryption and + * decryption + */ + protected byte n = 0; + protected byte[] P = null; + protected byte s = 0; + + protected byte[] workingIV; + protected byte[] workingKey; + + public String getAlgorithmName() + { + return "VMPC"; + } + + /** + * initialise a VMPC cipher. + * + * @param forEncryption + * whether or not we are for encryption. + * @param params + * the parameters required to set up the cipher. + * @exception IllegalArgumentException + * if the params argument is inappropriate. + */ + public void init(boolean forEncryption, CipherParameters params) + { + if (!(params instanceof ParametersWithIV)) + { + throw new IllegalArgumentException( + "VMPC init parameters must include an IV"); + } + + ParametersWithIV ivParams = (ParametersWithIV) params; + KeyParameter key = (KeyParameter) ivParams.getParameters(); + + if (!(ivParams.getParameters() instanceof KeyParameter)) + { + throw new IllegalArgumentException( + "VMPC init parameters must include a key"); + } + + this.workingIV = ivParams.getIV(); + + if (workingIV == null || workingIV.length < 1 || workingIV.length > 768) + { + throw new IllegalArgumentException("VMPC requires 1 to 768 bytes of IV"); + } + + this.workingKey = key.getKey(); + + initKey(this.workingKey, this.workingIV); + } + + protected void initKey(byte[] keyBytes, byte[] ivBytes) + { + s = 0; + P = new byte[256]; + for (int i = 0; i < 256; i++) + { + P[i] = (byte) i; + } + + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + keyBytes[m % keyBytes.length]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + ivBytes[m % ivBytes.length]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + n = 0; + } + + public void processBytes(byte[] in, int inOff, int len, byte[] out, + int outOff) + { + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + for (int i = 0; i < len; i++) + { + s = P[(s + P[n & 0xff]) & 0xff]; + byte z = P[(P[(P[s & 0xff]) & 0xff] + 1) & 0xff]; + // encryption + byte temp = P[n & 0xff]; + P[n & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + n = (byte) ((n + 1) & 0xff); + + // xor + out[i + outOff] = (byte) (in[i + inOff] ^ z); + } + } + + public void reset() + { + initKey(this.workingKey, this.workingIV); + } + + public byte returnByte(byte in) + { + s = P[(s + P[n & 0xff]) & 0xff]; + byte z = P[(P[(P[s & 0xff]) & 0xff] + 1) & 0xff]; + // encryption + byte temp = P[n & 0xff]; + P[n & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + n = (byte) ((n + 1) & 0xff); + + // xor + return (byte) (in ^ z); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/VMPCKSA3Engine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/VMPCKSA3Engine.java new file mode 100644 index 000000000..99d63f031 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/VMPCKSA3Engine.java @@ -0,0 +1,45 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +public class VMPCKSA3Engine extends VMPCEngine +{ + public String getAlgorithmName() + { + return "VMPC-KSA3"; + } + + protected void initKey(byte[] keyBytes, byte[] ivBytes) + { + s = 0; + P = new byte[256]; + for (int i = 0; i < 256; i++) + { + P[i] = (byte) i; + } + + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + keyBytes[m % keyBytes.length]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + ivBytes[m % ivBytes.length]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + keyBytes[m % keyBytes.length]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + + n = 0; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/engines/XTEAEngine.java b/src/com/google/bitcoin/bouncycastle/crypto/engines/XTEAEngine.java new file mode 100644 index 000000000..3e0417f97 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/engines/XTEAEngine.java @@ -0,0 +1,182 @@ +package com.google.bitcoin.bouncycastle.crypto.engines; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * An XTEA engine. + */ +public class XTEAEngine + implements BlockCipher +{ + private static final int rounds = 32, + block_size = 8, +// key_size = 16, + delta = 0x9E3779B9; + + /* + * the expanded key array of 4 subkeys + */ + private int[] _S = new int[4], + _sum0 = new int[32], + _sum1 = new int[32]; + private boolean _initialised, + _forEncryption; + + /** + * Create an instance of the TEA encryption algorithm + * and set some defaults + */ + public XTEAEngine() + { + _initialised = false; + } + + public String getAlgorithmName() + { + return "XTEA"; + } + + public int getBlockSize() + { + return block_size; + } + + /** + * initialise + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException("invalid parameter passed to TEA init - " + params.getClass().getName()); + } + + _forEncryption = forEncryption; + _initialised = true; + + KeyParameter p = (KeyParameter)params; + + setKey(p.getKey()); + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + if (!_initialised) + { + throw new IllegalStateException(getAlgorithmName()+" not initialised"); + } + + if ((inOff + block_size) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + block_size) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + return (_forEncryption) ? encryptBlock(in, inOff, out, outOff) + : decryptBlock(in, inOff, out, outOff); + } + + public void reset() + { + } + + /** + * Re-key the cipher. + *

+ * @param key the key to be used + */ + private void setKey( + byte[] key) + { + int i, j; + for (i = j = 0; i < 4; i++,j+=4) + { + _S[i] = bytesToInt(key, j); + } + + for (i = j = 0; i < rounds; i++) + { + _sum0[i] = (j + _S[j & 3]); + j += delta; + _sum1[i] = (j + _S[j >>> 11 & 3]); + } + } + + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + // Pack bytes into integers + int v0 = bytesToInt(in, inOff); + int v1 = bytesToInt(in, inOff + 4); + + for (int i = 0; i < rounds; i++) + { + v0 += ((v1 << 4 ^ v1 >>> 5) + v1) ^ _sum0[i]; + v1 += ((v0 << 4 ^ v0 >>> 5) + v0) ^ _sum1[i]; + } + + unpackInt(v0, out, outOff); + unpackInt(v1, out, outOff + 4); + + return block_size; + } + + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + { + // Pack bytes into integers + int v0 = bytesToInt(in, inOff); + int v1 = bytesToInt(in, inOff + 4); + + for (int i = rounds-1; i >= 0; i--) + { + v1 -= ((v0 << 4 ^ v0 >>> 5) + v0) ^ _sum1[i]; + v0 -= ((v1 << 4 ^ v1 >>> 5) + v1) ^ _sum0[i]; + } + + unpackInt(v0, out, outOff); + unpackInt(v1, out, outOff + 4); + + return block_size; + } + + private int bytesToInt(byte[] in, int inOff) + { + return ((in[inOff++]) << 24) | + ((in[inOff++] & 255) << 16) | + ((in[inOff++] & 255) << 8) | + ((in[inOff] & 255)); + } + + private void unpackInt(int v, byte[] out, int outOff) + { + out[outOff++] = (byte)(v >>> 24); + out[outOff++] = (byte)(v >>> 16); + out[outOff++] = (byte)(v >>> 8); + out[outOff ] = (byte)v; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/examples/DESExample.java b/src/com/google/bitcoin/bouncycastle/crypto/examples/DESExample.java new file mode 100644 index 000000000..8d4c7e895 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/examples/DESExample.java @@ -0,0 +1,419 @@ +package com.google.bitcoin.bouncycastle.crypto.examples; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.CryptoException; +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.engines.DESedeEngine; +import com.google.bitcoin.bouncycastle.crypto.generators.DESedeKeyGenerator; +import com.google.bitcoin.bouncycastle.crypto.modes.CBCBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.params.DESedeParameters; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.util.encoders.Hex; + +/** + * DESExample is a simple DES based encryptor/decryptor. + *

+ * The program is command line driven, with the input + * and output files specified on the command line. + *

+ * java org.bouncycastle.crypto.examples.DESExample infile outfile [keyfile]
+ * 
+ * A new key is generated for each encryption, if key is not specified, + * then the example will assume encryption is required, and as output + * create deskey.dat in the current directory. This key is a hex + * encoded byte-stream that is used for the decryption. The output + * file is Hex encoded, 60 characters wide text file. + *

+ * When encrypting; + *

    + *
  • the infile is expected to be a byte stream (text or binary) + *
  • there is no keyfile specified on the input line + *
+ *

+ * When decrypting; + *

  • the infile is expected to be the 60 character wide base64 + * encoded file + *
  • the keyfile is expected to be a base64 encoded file + *

    + * This example shows how to use the light-weight API, DES and + * the filesystem for message encryption and decryption. + * + */ +public class DESExample extends Object +{ + // Encrypting or decrypting ? + private boolean encrypt = true; + + // To hold the initialised DESede cipher + private PaddedBufferedBlockCipher cipher = null; + + // The input stream of bytes to be processed for encryption + private BufferedInputStream in = null; + + // The output stream of bytes to be procssed + private BufferedOutputStream out = null; + + // The key + private byte[] key = null; + + /* + * start the application + */ + public static void main(String[] args) + { + boolean encrypt = true; + String infile = null; + String outfile = null; + String keyfile = null; + + if (args.length < 2) + { + DESExample de = new DESExample(); + System.err.println("Usage: java "+de.getClass().getName()+ + " infile outfile [keyfile]"); + System.exit(1); + } + + keyfile = "deskey.dat"; + infile = args[0]; + outfile = args[1]; + + if (args.length > 2) + { + encrypt = false; + keyfile = args[2]; + } + + DESExample de = new DESExample(infile, outfile, keyfile, encrypt); + de.process(); + } + + // Default constructor, used for the usage message + public DESExample() + { + } + + /* + * Constructor, that takes the arguments appropriate for + * processing the command line directives. + */ + public DESExample( + String infile, + String outfile, + String keyfile, + boolean encrypt) + { + /* + * First, determine that infile & keyfile exist as appropriate. + * + * This will also create the BufferedInputStream as required + * for reading the input file. All input files are treated + * as if they are binary, even if they contain text, it's the + * bytes that are encrypted. + */ + this.encrypt = encrypt; + try + { + in = new BufferedInputStream(new FileInputStream(infile)); + } + catch (FileNotFoundException fnf) + { + System.err.println("Input file not found ["+infile+"]"); + System.exit(1); + } + + try + { + out = new BufferedOutputStream(new FileOutputStream(outfile)); + } + catch (IOException fnf) + { + System.err.println("Output file not created ["+outfile+"]"); + System.exit(1); + } + + if (encrypt) + { + try + { + /* + * The process of creating a new key requires a + * number of steps. + * + * First, create the parameters for the key generator + * which are a secure random number generator, and + * the length of the key (in bits). + */ + SecureRandom sr = null; + try + { + sr = new SecureRandom(); + /* + * This following call to setSeed() makes the + * initialisation of the SecureRandom object + * _very_ fast, but not secure AT ALL. + * + * Remove the line, recreate the class file and + * then run DESExample again to see the difference. + * + * The initialisation of a SecureRandom object + * can take 5 or more seconds depending on the + * CPU that the program is running on. That can + * be annoying during unit testing. + * -- jon + */ + sr.setSeed("www.bouncycastle.org".getBytes()); + } + catch (Exception nsa) + { + System.err.println("Hmmm, no SHA1PRNG, you need the "+ + "Sun implementation"); + System.exit(1); + } + KeyGenerationParameters kgp = new KeyGenerationParameters( + sr, + DESedeParameters.DES_EDE_KEY_LENGTH*8); + + /* + * Second, initialise the key generator with the parameters + */ + DESedeKeyGenerator kg = new DESedeKeyGenerator(); + kg.init(kgp); + + /* + * Third, and finally, generate the key + */ + key = kg.generateKey(); + + /* + * We can now output the key to the file, but first + * hex encode the key so that we can have a look + * at it with a text editor if we so desire + */ + BufferedOutputStream keystream = + new BufferedOutputStream(new FileOutputStream(keyfile)); + byte[] keyhex = Hex.encode(key); + keystream.write(keyhex, 0, keyhex.length); + keystream.flush(); + keystream.close(); + } + catch (IOException createKey) + { + System.err.println("Could not decryption create key file "+ + "["+keyfile+"]"); + System.exit(1); + } + } + else + { + try + { + // read the key, and decode from hex encoding + BufferedInputStream keystream = + new BufferedInputStream(new FileInputStream(keyfile)); + int len = keystream.available(); + byte[] keyhex = new byte[len]; + keystream.read(keyhex, 0, len); + key = Hex.decode(keyhex); + } + catch (IOException ioe) + { + System.err.println("Decryption key file not found, "+ + "or not valid ["+keyfile+"]"); + System.exit(1); + } + } + } + + private void process() + { + /* + * Setup the DESede cipher engine, create a PaddedBufferedBlockCipher + * in CBC mode. + */ + cipher = new PaddedBufferedBlockCipher( + new CBCBlockCipher(new DESedeEngine())); + + /* + * The input and output streams are currently set up + * appropriately, and the key bytes are ready to be + * used. + * + */ + + if (encrypt) + { + performEncrypt(key); + } + else + { + performDecrypt(key); + } + + // after processing clean up the files + try + { + in.close(); + out.flush(); + out.close(); + } + catch (IOException closing) + { + + } + } + + /* + * This method performs all the encryption and writes + * the cipher text to the buffered output stream created + * previously. + */ + private void performEncrypt(byte[] key) + { + // initialise the cipher with the key bytes, for encryption + cipher.init(true, new KeyParameter(key)); + + /* + * Create some temporary byte arrays for use in + * encryption, make them a reasonable size so that + * we don't spend forever reading small chunks from + * a file. + * + * There is no particular reason for using getBlockSize() + * to determine the size of the input chunk. It just + * was a convenient number for the example. + */ + // int inBlockSize = cipher.getBlockSize() * 5; + int inBlockSize = 47; + int outBlockSize = cipher.getOutputSize(inBlockSize); + + byte[] inblock = new byte[inBlockSize]; + byte[] outblock = new byte[outBlockSize]; + + /* + * now, read the file, and output the chunks + */ + try + { + int inL; + int outL; + byte[] rv = null; + while ((inL=in.read(inblock, 0, inBlockSize)) > 0) + { + outL = cipher.processBytes(inblock, 0, inL, outblock, 0); + /* + * Before we write anything out, we need to make sure + * that we've got something to write out. + */ + if (outL > 0) + { + rv = Hex.encode(outblock, 0, outL); + out.write(rv, 0, rv.length); + out.write('\n'); + } + } + + try + { + /* + * Now, process the bytes that are still buffered + * within the cipher. + */ + outL = cipher.doFinal(outblock, 0); + if (outL > 0) + { + rv = Hex.encode(outblock, 0, outL); + out.write(rv, 0, rv.length); + out.write('\n'); + } + } + catch (CryptoException ce) + { + + } + } + catch (IOException ioeread) + { + ioeread.printStackTrace(); + } + } + + /* + * This method performs all the decryption and writes + * the plain text to the buffered output stream created + * previously. + */ + private void performDecrypt(byte[] key) + { + // initialise the cipher for decryption + cipher.init(false, new KeyParameter(key)); + + /* + * As the decryption is from our preformatted file, + * and we know that it's a hex encoded format, then + * we wrap the InputStream with a BufferedReader + * so that we can read it easily. + */ + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + /* + * now, read the file, and output the chunks + */ + try + { + int outL; + byte[] inblock = null; + byte[] outblock = null; + String rv = null; + while ((rv = br.readLine()) != null) + { + inblock = Hex.decode(rv); + outblock = new byte[cipher.getOutputSize(inblock.length)]; + + outL = cipher.processBytes(inblock, 0, inblock.length, + outblock, 0); + /* + * Before we write anything out, we need to make sure + * that we've got something to write out. + */ + if (outL > 0) + { + out.write(outblock, 0, outL); + } + } + + try + { + /* + * Now, process the bytes that are still buffered + * within the cipher. + */ + outL = cipher.doFinal(outblock, 0); + if (outL > 0) + { + out.write(outblock, 0, outL); + } + } + catch (CryptoException ce) + { + + } + } + catch (IOException ioeread) + { + ioeread.printStackTrace(); + } + } + +} + diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/BaseKDFBytesGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/BaseKDFBytesGenerator.java new file mode 100644 index 000000000..33a941b8e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/BaseKDFBytesGenerator.java @@ -0,0 +1,142 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.DerivationFunction; +import com.google.bitcoin.bouncycastle.crypto.DerivationParameters; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.params.ISO18033KDFParameters; +import com.google.bitcoin.bouncycastle.crypto.params.KDFParameters; + +/** + * Basic KDF generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033 + *
    + * This implementation is based on ISO 18033/P1363a. + */ +public class BaseKDFBytesGenerator + implements DerivationFunction +{ + private int counterStart; + private Digest digest; + private byte[] shared; + private byte[] iv; + + /** + * Construct a KDF Parameters generator. + *

    + * @param counterStart value of counter. + * @param digest the digest to be used as the source of derived keys. + */ + protected BaseKDFBytesGenerator( + int counterStart, + Digest digest) + { + this.counterStart = counterStart; + this.digest = digest; + } + + public void init( + DerivationParameters param) + { + if (param instanceof KDFParameters) + { + KDFParameters p = (KDFParameters)param; + + shared = p.getSharedSecret(); + iv = p.getIV(); + } + else if (param instanceof ISO18033KDFParameters) + { + ISO18033KDFParameters p = (ISO18033KDFParameters)param; + + shared = p.getSeed(); + iv = null; + } + else + { + throw new IllegalArgumentException("KDF parameters required for KDF2Generator"); + } + } + + /** + * return the underlying digest. + */ + public Digest getDigest() + { + return digest; + } + + /** + * fill len bytes of the output buffer with bytes generated from + * the derivation function. + * + * @throws IllegalArgumentException if the size of the request will cause an overflow. + * @throws DataLengthException if the out buffer is too small. + */ + public int generateBytes( + byte[] out, + int outOff, + int len) + throws DataLengthException, IllegalArgumentException + { + if ((out.length - len) < outOff) + { + throw new DataLengthException("output buffer too small"); + } + + long oBytes = len; + int outLen = digest.getDigestSize(); + + // + // this is at odds with the standard implementation, the + // maximum value should be hBits * (2^32 - 1) where hBits + // is the digest output size in bits. We can't have an + // array with a long index at the moment... + // + if (oBytes > ((2L << 32) - 1)) + { + throw new IllegalArgumentException("Output length too large"); + } + + int cThreshold = (int)((oBytes + outLen - 1) / outLen); + + byte[] dig = null; + + dig = new byte[digest.getDigestSize()]; + + int counter = counterStart; + + for (int i = 0; i < cThreshold; i++) + { + digest.update(shared, 0, shared.length); + + digest.update((byte)(counter >> 24)); + digest.update((byte)(counter >> 16)); + digest.update((byte)(counter >> 8)); + digest.update((byte)counter); + + if (iv != null) + { + digest.update(iv, 0, iv.length); + } + + digest.doFinal(dig, 0); + + if (len > outLen) + { + System.arraycopy(dig, 0, out, outOff, outLen); + outOff += outLen; + len -= outLen; + } + else + { + System.arraycopy(dig, 0, out, outOff, len); + } + + counter++; + } + + digest.reset(); + + return len; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/DESKeyGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/DESKeyGenerator.java new file mode 100644 index 000000000..c9fdab153 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/DESKeyGenerator.java @@ -0,0 +1,48 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.CipherKeyGenerator; +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DESParameters; + +public class DESKeyGenerator + extends CipherKeyGenerator +{ + /** + * initialise the key generator - if strength is set to zero + * the key generated will be 64 bits in size, otherwise + * strength can be 64 or 56 bits (if you don't count the parity bits). + * + * @param param the parameters to be used for key generation + */ + public void init( + KeyGenerationParameters param) + { + super.init(param); + + if (strength == 0 || strength == (56 / 8)) + { + strength = DESParameters.DES_KEY_LENGTH; + } + else if (strength != DESParameters.DES_KEY_LENGTH) + { + throw new IllegalArgumentException("DES key must be " + + (DESParameters.DES_KEY_LENGTH * 8) + + " bits long."); + } + } + + public byte[] generateKey() + { + byte[] newKey = new byte[DESParameters.DES_KEY_LENGTH]; + + do + { + random.nextBytes(newKey); + + DESParameters.setOddParity(newKey); + } + while (DESParameters.isWeakKey(newKey, 0)); + + return newKey; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/DESedeKeyGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/DESedeKeyGenerator.java new file mode 100644 index 000000000..ef5b6e54a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/DESedeKeyGenerator.java @@ -0,0 +1,56 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DESedeParameters; + +public class DESedeKeyGenerator + extends DESKeyGenerator +{ + /** + * initialise the key generator - if strength is set to zero + * the key generated will be 192 bits in size, otherwise + * strength can be 128 or 192 (or 112 or 168 if you don't count + * parity bits), depending on whether you wish to do 2-key or 3-key + * triple DES. + * + * @param param the parameters to be used for key generation + */ + public void init( + KeyGenerationParameters param) + { + this.random = param.getRandom(); + this.strength = (param.getStrength() + 7) / 8; + + if (strength == 0 || strength == (168 / 8)) + { + strength = DESedeParameters.DES_EDE_KEY_LENGTH; + } + else if (strength == (112 / 8)) + { + strength = 2 * DESedeParameters.DES_KEY_LENGTH; + } + else if (strength != DESedeParameters.DES_EDE_KEY_LENGTH + && strength != (2 * DESedeParameters.DES_KEY_LENGTH)) + { + throw new IllegalArgumentException("DESede key must be " + + (DESedeParameters.DES_EDE_KEY_LENGTH * 8) + " or " + + (2 * 8 * DESedeParameters.DES_KEY_LENGTH) + + " bits long."); + } + } + + public byte[] generateKey() + { + byte[] newKey = new byte[strength]; + + do + { + random.nextBytes(newKey); + + DESedeParameters.setOddParity(newKey); + } + while (DESedeParameters.isWeakKey(newKey, 0, newKey.length)); + + return newKey; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/DHBasicKeyPairGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/DHBasicKeyPairGenerator.java new file mode 100644 index 000000000..140b1d453 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/DHBasicKeyPairGenerator.java @@ -0,0 +1,42 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPair; +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHKeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHPublicKeyParameters; + +import java.math.BigInteger; + +/** + * a basic Diffie-Hellman key pair generator. + * + * This generates keys consistent for use with the basic algorithm for + * Diffie-Hellman. + */ +public class DHBasicKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + private DHKeyGenerationParameters param; + + public void init( + KeyGenerationParameters param) + { + this.param = (DHKeyGenerationParameters)param; + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + DHKeyGeneratorHelper helper = DHKeyGeneratorHelper.INSTANCE; + DHParameters dhp = param.getParameters(); + + BigInteger x = helper.calculatePrivate(dhp, param.getRandom()); + BigInteger y = helper.calculatePublic(dhp, x); + + return new AsymmetricCipherKeyPair( + new DHPublicKeyParameters(y, dhp), + new DHPrivateKeyParameters(x, dhp)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/DHKeyGeneratorHelper.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/DHKeyGeneratorHelper.java new file mode 100644 index 000000000..7397934b8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/DHKeyGeneratorHelper.java @@ -0,0 +1,51 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.params.DHParameters; +import com.google.bitcoin.bouncycastle.util.BigIntegers; + +class DHKeyGeneratorHelper +{ + static final DHKeyGeneratorHelper INSTANCE = new DHKeyGeneratorHelper(); + + private static final BigInteger ONE = BigInteger.valueOf(1); + private static final BigInteger TWO = BigInteger.valueOf(2); + + private DHKeyGeneratorHelper() + { + } + + BigInteger calculatePrivate(DHParameters dhParams, SecureRandom random) + { + BigInteger p = dhParams.getP(); + int limit = dhParams.getL(); + + if (limit != 0) + { + return new BigInteger(limit, random).setBit(limit - 1); + } + + BigInteger min = TWO; + int m = dhParams.getM(); + if (m != 0) + { + min = ONE.shiftLeft(m - 1); + } + + BigInteger max = p.subtract(TWO); + BigInteger q = dhParams.getQ(); + if (q != null) + { + max = q.subtract(TWO); + } + + return BigIntegers.createRandomInRange(min, max, random); + } + + BigInteger calculatePublic(DHParameters dhParams, BigInteger x) + { + return dhParams.getG().modPow(x, dhParams.getP()); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/DHKeyPairGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/DHKeyPairGenerator.java new file mode 100644 index 000000000..0157f0b4d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/DHKeyPairGenerator.java @@ -0,0 +1,42 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPair; +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHKeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHPublicKeyParameters; + +import java.math.BigInteger; + +/** + * a Diffie-Hellman key pair generator. + * + * This generates keys consistent for use in the MTI/A0 key agreement protocol + * as described in "Handbook of Applied Cryptography", Pages 516-519. + */ +public class DHKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + private DHKeyGenerationParameters param; + + public void init( + KeyGenerationParameters param) + { + this.param = (DHKeyGenerationParameters)param; + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + DHKeyGeneratorHelper helper = DHKeyGeneratorHelper.INSTANCE; + DHParameters dhp = param.getParameters(); + + BigInteger x = helper.calculatePrivate(dhp, param.getRandom()); + BigInteger y = helper.calculatePublic(dhp, x); + + return new AsymmetricCipherKeyPair( + new DHPublicKeyParameters(y, dhp), + new DHPrivateKeyParameters(x, dhp)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/DHParametersGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/DHParametersGenerator.java new file mode 100644 index 000000000..b3e8aa3fa --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/DHParametersGenerator.java @@ -0,0 +1,52 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.params.DHParameters; + +import java.math.BigInteger; +import java.security.SecureRandom; + +public class DHParametersGenerator +{ + private int size; + private int certainty; + private SecureRandom random; + + private static final BigInteger TWO = BigInteger.valueOf(2); + + /** + * Initialise the parameters generator. + * + * @param size bit length for the prime p + * @param certainty level of certainty for the prime number tests + * @param random a source of randomness + */ + public void init( + int size, + int certainty, + SecureRandom random) + { + this.size = size; + this.certainty = certainty; + this.random = random; + } + + /** + * which generates the p and g values from the given parameters, + * returning the DHParameters object. + *

    + * Note: can take a while... + */ + public DHParameters generateParameters() + { + // + // find a safe prime p where p = 2*q + 1, where p and q are prime. + // + BigInteger[] safePrimes = DHParametersHelper.generateSafePrimes(size, certainty, random); + + BigInteger p = safePrimes[0]; + BigInteger q = safePrimes[1]; + BigInteger g = DHParametersHelper.selectGenerator(p, q, random); + + return new DHParameters(p, g, q, TWO, null); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/DHParametersHelper.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/DHParametersHelper.java new file mode 100644 index 000000000..2d032ceee --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/DHParametersHelper.java @@ -0,0 +1,70 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.util.BigIntegers; + +class DHParametersHelper +{ + private static final BigInteger ONE = BigInteger.valueOf(1); + private static final BigInteger TWO = BigInteger.valueOf(2); + + // Finds a pair of prime BigInteger's {p, q: p = 2q + 1} + static BigInteger[] generateSafePrimes( + int size, + int certainty, + SecureRandom random) + { + BigInteger p, q; + int qLength = size - 1; + + for (;;) + { + q = new BigInteger(qLength, 2, random); + + // p <- 2q + 1 + p = q.shiftLeft(1).add(ONE); + + if (p.isProbablePrime(certainty) + && (certainty <= 2 || q.isProbablePrime(certainty))) + { + break; + } + } + + return new BigInteger[] { p, q }; + } + + // Select a high order element of the multiplicative group Zp* + // p and q must be s.t. p = 2*q + 1, where p and q are prime + static BigInteger selectGenerator( + BigInteger p, + BigInteger q, + SecureRandom random) + { + BigInteger pMinusTwo = p.subtract(TWO); + BigInteger g; + + // Handbook of Applied Cryptography 4.86 + do + { + g = BigIntegers.createRandomInRange(TWO, pMinusTwo, random); + } + while (g.modPow(TWO, p).equals(ONE) + || g.modPow(q, p).equals(ONE)); + +/* + // RFC 2631 2.1.1 (and see Handbook of Applied Cryptography 4.81) + do + { + BigInteger h = createInRange(TWO, pMinusTwo, random); + + g = h.modPow(TWO, p); + } + while (g.equals(ONE)); +*/ + + return g; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/DSAKeyPairGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/DSAKeyPairGenerator.java new file mode 100644 index 000000000..bfeaf3fdc --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/DSAKeyPairGenerator.java @@ -0,0 +1,61 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPair; +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DSAKeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DSAParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DSAPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DSAPublicKeyParameters; +import com.google.bitcoin.bouncycastle.util.BigIntegers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * a DSA key pair generator. + * + * This generates DSA keys in line with the method described + * in FIPS 186-3 B.1 FFC Key Pair Generation. + */ +public class DSAKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + private static final BigInteger ONE = BigInteger.valueOf(1); + + private DSAKeyGenerationParameters param; + + public void init( + KeyGenerationParameters param) + { + this.param = (DSAKeyGenerationParameters)param; + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + DSAParameters dsaParams = param.getParameters(); + + BigInteger x = generatePrivateKey(dsaParams.getQ(), param.getRandom()); + BigInteger y = calculatePublicKey(dsaParams.getP(), dsaParams.getG(), x); + + return new AsymmetricCipherKeyPair( + new DSAPublicKeyParameters(y, dsaParams), + new DSAPrivateKeyParameters(x, dsaParams)); + } + + private static BigInteger generatePrivateKey(BigInteger q, SecureRandom random) + { + // TODO Prefer this method? (change test cases that used fixed random) + // B.1.1 Key Pair Generation Using Extra Random Bits +// BigInteger c = new BigInteger(q.bitLength() + 64, random); +// return c.mod(q.subtract(ONE)).add(ONE); + + // B.1.2 Key Pair Generation by Testing Candidates + return BigIntegers.createRandomInRange(ONE, q.subtract(ONE), random); + } + + private static BigInteger calculatePublicKey(BigInteger p, BigInteger g, BigInteger x) + { + return g.modPow(x, p); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/DSAParametersGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/DSAParametersGenerator.java new file mode 100644 index 000000000..49e3582d4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/DSAParametersGenerator.java @@ -0,0 +1,337 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.digests.SHA1Digest; +import com.google.bitcoin.bouncycastle.crypto.digests.SHA256Digest; +import com.google.bitcoin.bouncycastle.crypto.params.DSAParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DSAValidationParameters; +import com.google.bitcoin.bouncycastle.util.Arrays; +import com.google.bitcoin.bouncycastle.util.BigIntegers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +// TODO Update javadoc to mention FIPS 186-3 when done +/** + * generate suitable parameters for DSA, in line with FIPS 186-2. + */ +public class DSAParametersGenerator +{ + private int L, N; + private int certainty; + private SecureRandom random; + + private static final BigInteger ZERO = BigInteger.valueOf(0); + private static final BigInteger ONE = BigInteger.valueOf(1); + private static final BigInteger TWO = BigInteger.valueOf(2); + + /** + * initialise the key generator. + * + * @param size size of the key (range 2^512 -> 2^1024 - 64 bit increments) + * @param certainty measure of robustness of prime (for FIPS 186-2 compliance this should be at least 80). + * @param random random byte source. + */ + public void init( + int size, + int certainty, + SecureRandom random) + { + init(size, getDefaultN(size), certainty, random); + } + + // TODO Make public to enable support for DSA keys > 1024 bits + private void init( + int L, + int N, + int certainty, + SecureRandom random) + { + // TODO Check that the (L, N) pair is in the list of acceptable (L, N pairs) (see Section 4.2) + // TODO Should we enforce the minimum 'certainty' values as per C.3 Table C.1? + + this.L = L; + this.N = N; + this.certainty = certainty; + this.random = random; + } + + /** + * which generates the p and g values from the given parameters, + * returning the DSAParameters object. + *

    + * Note: can take a while... + */ + public DSAParameters generateParameters() + { + return L > 1024 + ? generateParameters_FIPS186_3() + : generateParameters_FIPS186_2(); + } + + private DSAParameters generateParameters_FIPS186_2() + { + byte[] seed = new byte[20]; + byte[] part1 = new byte[20]; + byte[] part2 = new byte[20]; + byte[] u = new byte[20]; + SHA1Digest sha1 = new SHA1Digest(); + int n = (L - 1) / 160; + byte[] w = new byte[L / 8]; + + for (;;) + { + random.nextBytes(seed); + + hash(sha1, seed, part1); + System.arraycopy(seed, 0, part2, 0, seed.length); + inc(part2); + hash(sha1, part2, part2); + + for (int i = 0; i != u.length; i++) + { + u[i] = (byte)(part1[i] ^ part2[i]); + } + + u[0] |= (byte)0x80; + u[19] |= (byte)0x01; + + BigInteger q = new BigInteger(1, u); + + if (!q.isProbablePrime(certainty)) + { + continue; + } + + byte[] offset = Arrays.clone(seed); + inc(offset); + + for (int counter = 0; counter < 4096; ++counter) + { + for (int k = 0; k < n; k++) + { + inc(offset); + hash(sha1, offset, part1); + System.arraycopy(part1, 0, w, w.length - (k + 1) * part1.length, part1.length); + } + + inc(offset); + hash(sha1, offset, part1); + System.arraycopy(part1, part1.length - ((w.length - (n) * part1.length)), w, 0, w.length - n * part1.length); + + w[0] |= (byte)0x80; + + BigInteger x = new BigInteger(1, w); + + BigInteger c = x.mod(q.shiftLeft(1)); + + BigInteger p = x.subtract(c.subtract(ONE)); + + if (p.bitLength() != L) + { + continue; + } + + if (p.isProbablePrime(certainty)) + { + BigInteger g = calculateGenerator_FIPS186_2(p, q, random); + + return new DSAParameters(p, q, g, new DSAValidationParameters(seed, counter)); + } + } + } + } + + private static BigInteger calculateGenerator_FIPS186_2(BigInteger p, BigInteger q, SecureRandom r) + { + BigInteger e = p.subtract(ONE).divide(q); + BigInteger pSub2 = p.subtract(TWO); + + for (;;) + { + BigInteger h = BigIntegers.createRandomInRange(TWO, pSub2, r); + BigInteger g = h.modPow(e, p); + if (g.bitLength() > 1) + { + return g; + } + } + } + + /** + * generate suitable parameters for DSA, in line with + * FIPS 186-3 A.1 Generation of the FFC Primes p and q. + */ + private DSAParameters generateParameters_FIPS186_3() + { +// A.1.1.2 Generation of the Probable Primes p and q Using an Approved Hash Function + // FIXME This should be configurable (digest size in bits must be >= N) + Digest d = new SHA256Digest(); + int outlen = d.getDigestSize() * 8; + +// 1. Check that the (L, N) pair is in the list of acceptable (L, N pairs) (see Section 4.2). If +// the pair is not in the list, then return INVALID. + // Note: checked at initialisation + +// 2. If (seedlen < N), then return INVALID. + // FIXME This should be configurable (must be >= N) + int seedlen = N; + byte[] seed = new byte[seedlen / 8]; + +// 3. n = ⎡L ⁄ outlen⎤ – 1. + int n = (L - 1) / outlen; + +// 4. b = L – 1 – (n ∗ outlen). + int b = (L - 1) % outlen; + + byte[] output = new byte[d.getDigestSize()]; + for (;;) + { +// 5. Get an arbitrary sequence of seedlen bits as the domain_parameter_seed. + random.nextBytes(seed); + +// 6. U = Hash (domain_parameter_seed) mod 2^(N–1). + hash(d, seed, output); + BigInteger U = new BigInteger(1, output).mod(ONE.shiftLeft(N - 1)); + +// 7. q = 2^(N–1) + U + 1 – ( U mod 2). + BigInteger q = ONE.shiftLeft(N - 1).add(U).add(ONE).subtract(U.mod(TWO)); + +// 8. Test whether or not q is prime as specified in Appendix C.3. + // TODO Review C.3 for primality checking + if (!q.isProbablePrime(certainty)) + { +// 9. If q is not a prime, then go to step 5. + continue; + } + +// 10. offset = 1. + // Note: 'offset' value managed incrementally + byte[] offset = Arrays.clone(seed); + +// 11. For counter = 0 to (4L – 1) do + int counterLimit = 4 * L; + for (int counter = 0; counter < counterLimit; ++counter) + { +// 11.1 For j = 0 to n do +// Vj = Hash ((domain_parameter_seed + offset + j) mod 2^seedlen). +// 11.2 W = V0 + (V1 ∗ 2^outlen) + ... + (V^(n–1) ∗ 2^((n–1) ∗ outlen)) + ((Vn mod 2^b) ∗ 2^(n ∗ outlen)). + // TODO Assemble w as a byte array + BigInteger W = ZERO; + for (int j = 0, exp = 0; j <= n; ++j, exp += outlen) + { + inc(offset); + hash(d, offset, output); + + BigInteger Vj = new BigInteger(1, output); + if (j == n) + { + Vj = Vj.mod(ONE.shiftLeft(b)); + } + + W = W.add(Vj.shiftLeft(exp)); + } + +// 11.3 X = W + 2^(L–1). Comment: 0 ≤ W < 2L–1; hence, 2L–1 ≤ X < 2L. + BigInteger X = W.add(ONE.shiftLeft(L - 1)); + +// 11.4 c = X mod 2q. + BigInteger c = X.mod(q.shiftLeft(1)); + +// 11.5 p = X - (c - 1). Comment: p ≡ 1 (mod 2q). + BigInteger p = X.subtract(c.subtract(ONE)); + +// 11.6 If (p < 2^(L - 1)), then go to step 11.9 + if (p.bitLength() != L) + { + continue; + } + +// 11.7 Test whether or not p is prime as specified in Appendix C.3. + // TODO Review C.3 for primality checking + if (p.isProbablePrime(certainty)) + { +// 11.8 If p is determined to be prime, then return VALID and the values of p, q and +// (optionally) the values of domain_parameter_seed and counter. + // TODO Make configurable (8-bit unsigned)? +// int index = 1; +// BigInteger g = calculateGenerator_FIPS186_3_Verifiable(d, p, q, seed, index); +// if (g != null) +// { +// // TODO Should 'index' be a part of the validation parameters? +// return new DSAParameters(p, q, g, new DSAValidationParameters(seed, counter)); +// } + + BigInteger g = calculateGenerator_FIPS186_3_Unverifiable(p, q, random); + return new DSAParameters(p, q, g, new DSAValidationParameters(seed, counter)); + } + +// 11.9 offset = offset + n + 1. Comment: Increment offset; then, as part of +// the loop in step 11, increment counter; if +// counter < 4L, repeat steps 11.1 through 11.8. + // Note: 'offset' value already incremented in inner loop + } +// 12. Go to step 5. + } + } + + private static BigInteger calculateGenerator_FIPS186_3_Unverifiable(BigInteger p, BigInteger q, + SecureRandom r) + { + return calculateGenerator_FIPS186_2(p, q, r); + } + +// private static BigInteger calculateGenerator_FIPS186_3_Verifiable(Digest d, BigInteger p, BigInteger q, +// byte[] seed, int index) +// { +//// A.2.3 Verifiable Canonical Generation of the Generator g +// BigInteger e = p.subtract(ONE).divide(q); +// byte[] ggen = Hex.decode("6767656E"); +// +// // 7. U = domain_parameter_seed || “ggen” || index || count. +// byte[] U = new byte[seed.length + ggen.length + 1 + 2]; +// System.arraycopy(seed, 0, U, 0, seed.length); +// System.arraycopy(ggen, 0, U, seed.length, ggen.length); +// U[U.length - 3] = (byte)index; +// +// byte[] w = new byte[d.getDigestSize()]; +// for (int count = 1; count < (1 << 16); ++count) +// { +// inc(U); +// hash(d, U, w); +// BigInteger W = new BigInteger(1, w); +// BigInteger g = W.modPow(e, p); +// if (g.compareTo(TWO) >= 0) +// { +// return g; +// } +// } +// +// return null; +// } + + private static void hash(Digest d, byte[] input, byte[] output) + { + d.update(input, 0, input.length); + d.doFinal(output, 0); + } + + private static int getDefaultN(int L) + { + return L > 1024 ? 256 : 160; + } + + private static void inc(byte[] buf) + { + for (int i = buf.length - 1; i >= 0; --i) + { + byte b = (byte)((buf[i] + 1) & 0xff); + buf[i] = b; + + if (b != 0) + { + break; + } + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/ECKeyPairGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/ECKeyPairGenerator.java new file mode 100644 index 000000000..f9bb0b25e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/ECKeyPairGenerator.java @@ -0,0 +1,53 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPair; +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECDomainParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECKeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPublicKeyParameters; +import com.google.bitcoin.bouncycastle.math.ec.ECConstants; +import com.google.bitcoin.bouncycastle.math.ec.ECPoint; + +public class ECKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator, ECConstants +{ + ECDomainParameters params; + SecureRandom random; + + public void init( + KeyGenerationParameters param) + { + ECKeyGenerationParameters ecP = (ECKeyGenerationParameters)param; + + this.random = ecP.getRandom(); + this.params = ecP.getDomainParameters(); + } + + /** + * Given the domain parameters this routine generates an EC key + * pair in accordance with X9.62 section 5.2.1 pages 26, 27. + */ + public AsymmetricCipherKeyPair generateKeyPair() + { + BigInteger n = params.getN(); + int nBitLength = n.bitLength(); + BigInteger d; + + do + { + d = new BigInteger(nBitLength, random); + } + while (d.equals(ZERO) || (d.compareTo(n) >= 0)); + + ECPoint Q = params.getG().multiply(d); + + return new AsymmetricCipherKeyPair( + new ECPublicKeyParameters(Q, params), + new ECPrivateKeyParameters(d, params)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/ElGamalKeyPairGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/ElGamalKeyPairGenerator.java new file mode 100644 index 000000000..ab5b18946 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/ElGamalKeyPairGenerator.java @@ -0,0 +1,44 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPair; +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ElGamalKeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ElGamalParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ElGamalPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ElGamalPublicKeyParameters; + +/** + * a ElGamal key pair generator. + *

    + * This generates keys consistent for use with ElGamal as described in + * page 164 of "Handbook of Applied Cryptography". + */ +public class ElGamalKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + private ElGamalKeyGenerationParameters param; + + public void init( + KeyGenerationParameters param) + { + this.param = (ElGamalKeyGenerationParameters)param; + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + DHKeyGeneratorHelper helper = DHKeyGeneratorHelper.INSTANCE; + ElGamalParameters egp = param.getParameters(); + DHParameters dhp = new DHParameters(egp.getP(), egp.getG(), null, egp.getL()); + + BigInteger x = helper.calculatePrivate(dhp, param.getRandom()); + BigInteger y = helper.calculatePublic(dhp, x); + + return new AsymmetricCipherKeyPair( + new ElGamalPublicKeyParameters(y, egp), + new ElGamalPrivateKeyParameters(x, egp)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/ElGamalParametersGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/ElGamalParametersGenerator.java new file mode 100644 index 000000000..e5f7460a1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/ElGamalParametersGenerator.java @@ -0,0 +1,43 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.params.ElGamalParameters; + +import java.math.BigInteger; +import java.security.SecureRandom; + +public class ElGamalParametersGenerator +{ + private int size; + private int certainty; + private SecureRandom random; + + public void init( + int size, + int certainty, + SecureRandom random) + { + this.size = size; + this.certainty = certainty; + this.random = random; + } + + /** + * which generates the p and g values from the given parameters, + * returning the ElGamalParameters object. + *

    + * Note: can take a while... + */ + public ElGamalParameters generateParameters() + { + // + // find a safe prime p where p = 2*q + 1, where p and q are prime. + // + BigInteger[] safePrimes = DHParametersHelper.generateSafePrimes(size, certainty, random); + + BigInteger p = safePrimes[0]; + BigInteger q = safePrimes[1]; + BigInteger g = DHParametersHelper.selectGenerator(p, q, random); + + return new ElGamalParameters(p, g); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/GOST3410KeyPairGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/GOST3410KeyPairGenerator.java new file mode 100644 index 000000000..00e4cea2e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/GOST3410KeyPairGenerator.java @@ -0,0 +1,57 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPair; +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.GOST3410KeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.GOST3410Parameters; +import com.google.bitcoin.bouncycastle.crypto.params.GOST3410PrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.GOST3410PublicKeyParameters; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * a GOST3410 key pair generator. + * This generates GOST3410 keys in line with the method described + * in GOST R 34.10-94. + */ +public class GOST3410KeyPairGenerator + implements AsymmetricCipherKeyPairGenerator + { + private static final BigInteger ZERO = BigInteger.valueOf(0); + + private GOST3410KeyGenerationParameters param; + + public void init( + KeyGenerationParameters param) + { + this.param = (GOST3410KeyGenerationParameters)param; + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + BigInteger p, q, a, x, y; + GOST3410Parameters GOST3410Params = param.getParameters(); + SecureRandom random = param.getRandom(); + + q = GOST3410Params.getQ(); + p = GOST3410Params.getP(); + a = GOST3410Params.getA(); + + do + { + x = new BigInteger(256, random); + } + while (x.equals(ZERO) || x.compareTo(q) >= 0); + + // + // calculate the public key. + // + y = a.modPow(x, p); + + return new AsymmetricCipherKeyPair( + new GOST3410PublicKeyParameters(y, GOST3410Params), + new GOST3410PrivateKeyParameters(x, GOST3410Params)); + } + } diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/GOST3410ParametersGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/GOST3410ParametersGenerator.java new file mode 100644 index 000000000..d90960aee --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/GOST3410ParametersGenerator.java @@ -0,0 +1,541 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.params.GOST3410Parameters; +import com.google.bitcoin.bouncycastle.crypto.params.GOST3410ValidationParameters; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * generate suitable parameters for GOST3410. + */ +public class GOST3410ParametersGenerator +{ + private int size; + private int typeproc; + private SecureRandom init_random; + + private static final BigInteger ONE = BigInteger.valueOf(1); + private static final BigInteger TWO = BigInteger.valueOf(2); + + /** + * initialise the key generator. + * + * @param size size of the key + * @param typeproc type procedure A,B = 1; A',B' - else + * @param random random byte source. + */ + public void init( + int size, + int typeproc, + SecureRandom random) + { + this.size = size; + this.typeproc = typeproc; + this.init_random = random; + } + + //Procedure A + private int procedure_A(int x0, int c, BigInteger[] pq, int size) + { + //Verify and perform condition: 065536) + { + x0 = init_random.nextInt()/32768; + } + + while((c<0 || c>65536) || (c/2==0)) + { + c = init_random.nextInt()/32768 + 1; + } + + BigInteger C = new BigInteger(Integer.toString(c)); + BigInteger constA16 = new BigInteger("19381"); + + //step1 + BigInteger[] y = new BigInteger[1]; // begin length = 1 + y[0] = new BigInteger(Integer.toString(x0)); + + //step 2 + int[] t = new int[1]; // t - orders; begin length = 1 + t[0] = size; + int s = 0; + for (int i=0; t[i]>=17; i++) + { + // extension array t + int tmp_t[] = new int[t.length + 1]; /////////////// + System.arraycopy(t,0,tmp_t,0,t.length); // extension + t = new int[tmp_t.length]; // array t + System.arraycopy(tmp_t, 0, t, 0, tmp_t.length); /////////////// + + t[i+1] = t[i]/2; + s = i+1; + } + + //step3 + BigInteger p[] = new BigInteger[s+1]; + p[s] = new BigInteger("8003",16); //set min prime number length 16 bit + + int m = s-1; //step4 + + for (int i=0; i=0) + { + break; //step 14 + } + else + { + pq[0] = p[0]; + pq[1] = p[1]; + return y[0].intValue(); //return for procedure B step 2 + } + } + } + return y[0].intValue(); + } + + //Procedure A' + private long procedure_Aa(long x0, long c, BigInteger[] pq, int size) + { + //Verify and perform condition: 04294967296L) + { + x0 = init_random.nextInt()*2; + } + + while((c<0 || c>4294967296L) || (c/2==0)) + { + c = init_random.nextInt()*2+1; + } + + BigInteger C = new BigInteger(Long.toString(c)); + BigInteger constA32 = new BigInteger("97781173"); + + //step1 + BigInteger[] y = new BigInteger[1]; // begin length = 1 + y[0] = new BigInteger(Long.toString(x0)); + + //step 2 + int[] t = new int[1]; // t - orders; begin length = 1 + t[0] = size; + int s = 0; + for (int i=0; t[i]>=33; i++) + { + // extension array t + int tmp_t[] = new int[t.length + 1]; /////////////// + System.arraycopy(t,0,tmp_t,0,t.length); // extension + t = new int[tmp_t.length]; // array t + System.arraycopy(tmp_t, 0, t, 0, tmp_t.length); /////////////// + + t[i+1] = t[i]/2; + s = i+1; + } + + //step3 + BigInteger p[] = new BigInteger[s+1]; + p[s] = new BigInteger("8000000B",16); //set min prime number length 32 bit + + int m = s-1; //step4 + + for (int i=0; i=0) + { + break; //step 14 + } + else + { + pq[0] = p[0]; + pq[1] = p[1]; + return y[0].longValue(); //return for procedure B' step 2 + } + } + } + return y[0].longValue(); + } + + //Procedure B + private void procedure_B(int x0, int c, BigInteger[] pq) + { + //Verify and perform condition: 065536) + { + x0 = init_random.nextInt()/32768; + } + + while((c<0 || c>65536) || (c/2==0)) + { + c = init_random.nextInt()/32768 + 1; + } + + BigInteger [] qp = new BigInteger[2]; + BigInteger q = null, Q = null, p = null; + BigInteger C = new BigInteger(Integer.toString(c)); + BigInteger constA16 = new BigInteger("19381"); + + //step1 + x0 = procedure_A(x0, c, qp, 256); + q = qp[0]; + + //step2 + x0 = procedure_A(x0, c, qp, 512); + Q = qp[0]; + + BigInteger[] y = new BigInteger[65]; + y[0] = new BigInteger(Integer.toString(x0)); + + int tp = 1024; + + step3: for(;;) + { + //step 3 + for (int j=0; j<64; j++) + { + y[j+1] = (y[j].multiply(constA16).add(C)).mod(TWO.pow(16)); + } + + //step 4 + BigInteger Y = new BigInteger("0"); + + for (int j=0; j<64; j++) + { + Y = Y.add(y[j].multiply(TWO.pow(16*j))); + } + + y[0] = y[64]; //step 5 + + //step 6 + BigInteger N = TWO.pow(tp-1).divide(q.multiply(Q)). + add((TWO.pow(tp-1).multiply(Y)). + divide(q.multiply(Q).multiply(TWO.pow(1024)))); + + if (N.mod(TWO).compareTo(ONE)==0) + { + N = N.add(ONE); + } + + int k = 0; //step 7 + + step8: for(;;) + { + //step 11 + p = q.multiply(Q).multiply(N.add(BigInteger.valueOf(k))).add(ONE); + + if (p.compareTo(TWO.pow(tp))==1) + { + continue step3; //step 9 + } + + //step10 + if ((TWO.modPow(q.multiply(Q).multiply(N.add(BigInteger.valueOf(k))),p).compareTo(ONE)==0) && + (TWO.modPow(q.multiply(N.add(BigInteger.valueOf(k))),p).compareTo(ONE)!=0)) + { + pq[0] = p; + pq[1] = q; + return; + } + else + { + k += 2; + continue step8; + } + } + } + } + + //Procedure B' + private void procedure_Bb(long x0, long c, BigInteger[] pq) + { + //Verify and perform condition: 04294967296L) + { + x0 = init_random.nextInt()*2; + } + + while((c<0 || c>4294967296L) || (c/2==0)) + { + c = init_random.nextInt()*2+1; + } + + BigInteger [] qp = new BigInteger[2]; + BigInteger q = null, Q = null, p = null; + BigInteger C = new BigInteger(Long.toString(c)); + BigInteger constA32 = new BigInteger("97781173"); + + //step1 + x0 = procedure_Aa(x0, c, qp, 256); + q = qp[0]; + + //step2 + x0 = procedure_Aa(x0, c, qp, 512); + Q = qp[0]; + + BigInteger[] y = new BigInteger[33]; + y[0] = new BigInteger(Long.toString(x0)); + + int tp = 1024; + + step3: for(;;) + { + //step 3 + for (int j=0; j<32; j++) + { + y[j+1] = (y[j].multiply(constA32).add(C)).mod(TWO.pow(32)); + } + + //step 4 + BigInteger Y = new BigInteger("0"); + for (int j=0; j<32; j++) + { + Y = Y.add(y[j].multiply(TWO.pow(32*j))); + } + + y[0] = y[32]; //step 5 + + //step 6 + BigInteger N = TWO.pow(tp-1).divide(q.multiply(Q)). + add((TWO.pow(tp-1).multiply(Y)). + divide(q.multiply(Q).multiply(TWO.pow(1024)))); + + if (N.mod(TWO).compareTo(ONE)==0) + { + N = N.add(ONE); + } + + int k = 0; //step 7 + + step8: for(;;) + { + //step 11 + p = q.multiply(Q).multiply(N.add(BigInteger.valueOf(k))).add(ONE); + + if (p.compareTo(TWO.pow(tp))==1) + { + continue step3; //step 9 + } + + //step10 + if ((TWO.modPow(q.multiply(Q).multiply(N.add(BigInteger.valueOf(k))),p).compareTo(ONE)==0) && + (TWO.modPow(q.multiply(N.add(BigInteger.valueOf(k))),p).compareTo(ONE)!=0)) + { + pq[0] = p; + pq[1] = q; + return; + } + else + { + k += 2; + continue step8; + } + } + } + } + + + /** + * Procedure C + * procedure generates the a value from the given p,q, + * returning the a value. + */ + private BigInteger procedure_C(BigInteger p, BigInteger q) + { + BigInteger pSub1 = p.subtract(ONE); + BigInteger pSub1DivQ = pSub1.divide(q); + int length = p.bitLength(); + + for(;;) + { + BigInteger d = new BigInteger(length, init_random); + + // 1 < d < p-1 + if (d.compareTo(ONE) > 0 && d.compareTo(pSub1) < 0) + { + BigInteger a = d.modPow(pSub1DivQ, p); + + if (a.compareTo(ONE) != 0) + { + return a; + } + } + } + } + + /** + * which generates the p , q and a values from the given parameters, + * returning the GOST3410Parameters object. + */ + public GOST3410Parameters generateParameters() + { + BigInteger [] pq = new BigInteger[2]; + BigInteger q = null, p = null, a = null; + + int x0, c; + long x0L, cL; + + if (typeproc==1) + { + x0 = init_random.nextInt(); + c = init_random.nextInt(); + + switch(size) + { + case 512: + procedure_A(x0, c, pq, 512); + break; + case 1024: + procedure_B(x0, c, pq); + break; + default: + throw new IllegalArgumentException("Ooops! key size 512 or 1024 bit."); + } + p = pq[0]; q = pq[1]; + a = procedure_C(p, q); + //System.out.println("p:"+p.toString(16)+"\n"+"q:"+q.toString(16)+"\n"+"a:"+a.toString(16)); + //System.out.println("p:"+p+"\n"+"q:"+q+"\n"+"a:"+a); + return new GOST3410Parameters(p, q, a, new GOST3410ValidationParameters(x0, c)); + } + else + { + x0L = init_random.nextLong(); + cL = init_random.nextLong(); + + switch(size) + { + case 512: + procedure_Aa(x0L, cL, pq, 512); + break; + case 1024: + procedure_Bb(x0L, cL, pq); + break; + default: + throw new IllegalStateException("Ooops! key size 512 or 1024 bit."); + } + p = pq[0]; q = pq[1]; + a = procedure_C(p, q); + //System.out.println("p:"+p.toString(16)+"\n"+"q:"+q.toString(16)+"\n"+"a:"+a.toString(16)); + //System.out.println("p:"+p+"\n"+"q:"+q+"\n"+"a:"+a); + return new GOST3410Parameters(p, q, a, new GOST3410ValidationParameters(x0L, cL)); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/KDF1BytesGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/KDF1BytesGenerator.java new file mode 100644 index 000000000..6d934b6af --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/KDF1BytesGenerator.java @@ -0,0 +1,23 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.Digest; + +/** + * KDF1 generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033 + *
    + * This implementation is based on ISO 18033/IEEE P1363a. + */ +public class KDF1BytesGenerator + extends BaseKDFBytesGenerator +{ + /** + * Construct a KDF1 byte generator. + *

    + * @param digest the digest to be used as the source of derived keys. + */ + public KDF1BytesGenerator( + Digest digest) + { + super(0, digest); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/KDF2BytesGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/KDF2BytesGenerator.java new file mode 100644 index 000000000..c8c0af10a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/KDF2BytesGenerator.java @@ -0,0 +1,24 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.Digest; + +/** + * KFD2 generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033 + *
    + * This implementation is based on IEEE P1363/ISO 18033. + */ +public class KDF2BytesGenerator + extends BaseKDFBytesGenerator +{ + /** + * Construct a KDF2 bytes generator. Generates key material + * according to IEEE P1363 or ISO 18033 depending on the initialisation. + *

    + * @param digest the digest to be used as the source of derived keys. + */ + public KDF2BytesGenerator( + Digest digest) + { + super(1, digest); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/MGF1BytesGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/MGF1BytesGenerator.java new file mode 100644 index 000000000..1c6d79a14 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/MGF1BytesGenerator.java @@ -0,0 +1,114 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.DerivationFunction; +import com.google.bitcoin.bouncycastle.crypto.DerivationParameters; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.params.MGFParameters; + +/** + * Generator for MGF1 as defined in PKCS 1v2 + */ +public class MGF1BytesGenerator + implements DerivationFunction +{ + private Digest digest; + private byte[] seed; + private int hLen; + + /** + * @param digest the digest to be used as the source of generated bytes + */ + public MGF1BytesGenerator( + Digest digest) + { + this.digest = digest; + this.hLen = digest.getDigestSize(); + } + + public void init( + DerivationParameters param) + { + if (!(param instanceof MGFParameters)) + { + throw new IllegalArgumentException("MGF parameters required for MGF1Generator"); + } + + MGFParameters p = (MGFParameters)param; + + seed = p.getSeed(); + } + + /** + * return the underlying digest. + */ + public Digest getDigest() + { + return digest; + } + + /** + * int to octet string. + */ + private void ItoOSP( + int i, + byte[] sp) + { + sp[0] = (byte)(i >>> 24); + sp[1] = (byte)(i >>> 16); + sp[2] = (byte)(i >>> 8); + sp[3] = (byte)(i >>> 0); + } + + /** + * fill len bytes of the output buffer with bytes generated from + * the derivation function. + * + * @throws DataLengthException if the out buffer is too small. + */ + public int generateBytes( + byte[] out, + int outOff, + int len) + throws DataLengthException, IllegalArgumentException + { + if ((out.length - len) < outOff) + { + throw new DataLengthException("output buffer too small"); + } + + byte[] hashBuf = new byte[hLen]; + byte[] C = new byte[4]; + int counter = 0; + + digest.reset(); + + if (len > hLen) + { + do + { + ItoOSP(counter, C); + + digest.update(seed, 0, seed.length); + digest.update(C, 0, C.length); + digest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, out, outOff + counter * hLen, hLen); + } + while (++counter < (len / hLen)); + } + + if ((counter * hLen) < len) + { + ItoOSP(counter, C); + + digest.update(seed, 0, seed.length); + digest.update(C, 0, C.length); + digest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, out, outOff + counter * hLen, len - (counter * hLen)); + } + + return len; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/NaccacheSternKeyPairGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/NaccacheSternKeyPairGenerator.java new file mode 100644 index 000000000..d645ae2da --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/NaccacheSternKeyPairGenerator.java @@ -0,0 +1,365 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPair; +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.NaccacheSternKeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.NaccacheSternKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.NaccacheSternPrivateKeyParameters; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.Vector; + +/** + * Key generation parameters for NaccacheStern cipher. For details on this cipher, please see + * + * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + */ +public class NaccacheSternKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + + private static int[] smallPrimes = + { + 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, + 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, + 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, + 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, + 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, + 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, + 541, 547, 557 + }; + + private NaccacheSternKeyGenerationParameters param; + + private static final BigInteger ONE = BigInteger.valueOf(1); // JDK 1.1 compatibility + + /* + * (non-Javadoc) + * + * @see org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator#init(org.bouncycastle.crypto.KeyGenerationParameters) + */ + public void init(KeyGenerationParameters param) + { + this.param = (NaccacheSternKeyGenerationParameters)param; + } + + /* + * (non-Javadoc) + * + * @see org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator#generateKeyPair() + */ + public AsymmetricCipherKeyPair generateKeyPair() + { + int strength = param.getStrength(); + SecureRandom rand = param.getRandom(); + int certainty = param.getCertainty(); + boolean debug = param.isDebug(); + + if (debug) + { + System.out.println("Fetching first " + param.getCntSmallPrimes() + " primes."); + } + + Vector smallPrimes = findFirstPrimes(param.getCntSmallPrimes()); + smallPrimes = permuteList(smallPrimes, rand); + + BigInteger u = ONE; + BigInteger v = ONE; + + for (int i = 0; i < smallPrimes.size() / 2; i++) + { + u = u.multiply((BigInteger)smallPrimes.elementAt(i)); + } + for (int i = smallPrimes.size() / 2; i < smallPrimes.size(); i++) + { + v = v.multiply((BigInteger)smallPrimes.elementAt(i)); + } + + BigInteger sigma = u.multiply(v); + + // n = (2 a u p_ + 1 ) ( 2 b v q_ + 1) + // -> |n| = strength + // |2| = 1 in bits + // -> |a| * |b| = |n| - |u| - |v| - |p_| - |q_| - |2| -|2| + // remainingStrength = strength - sigma.bitLength() - p_.bitLength() - + // q_.bitLength() - 1 -1 + int remainingStrength = strength - sigma.bitLength() - 48; + BigInteger a = generatePrime(remainingStrength / 2 + 1, certainty, rand); + BigInteger b = generatePrime(remainingStrength / 2 + 1, certainty, rand); + + BigInteger p_; + BigInteger q_; + BigInteger p; + BigInteger q; + long tries = 0; + if (debug) + { + System.out.println("generating p and q"); + } + + BigInteger _2au = a.multiply(u).shiftLeft(1); + BigInteger _2bv = b.multiply(v).shiftLeft(1); + + for (;;) + { + tries++; + + p_ = generatePrime(24, certainty, rand); + + p = p_.multiply(_2au).add(ONE); + + if (!p.isProbablePrime(certainty)) + { + continue; + } + + for (;;) + { + q_ = generatePrime(24, certainty, rand); + + if (p_.equals(q_)) + { + continue; + } + + q = q_.multiply(_2bv).add(ONE); + + if (q.isProbablePrime(certainty)) + { + break; + } + } + + if (!sigma.gcd(p_.multiply(q_)).equals(ONE)) + { + // System.out.println("sigma.gcd(p_.mult(q_)) != 1!\n p_: " + p_ + // +"\n q_: "+ q_ ); + continue; + } + + if (p.multiply(q).bitLength() < strength) + { + if (debug) + { + System.out.println("key size too small. Should be " + strength + " but is actually " + + p.multiply(q).bitLength()); + } + continue; + } + break; + } + + if (debug) + { + System.out.println("needed " + tries + " tries to generate p and q."); + } + + BigInteger n = p.multiply(q); + BigInteger phi_n = p.subtract(ONE).multiply(q.subtract(ONE)); + BigInteger g; + tries = 0; + if (debug) + { + System.out.println("generating g"); + } + for (;;) + { + + Vector gParts = new Vector(); + for (int ind = 0; ind != smallPrimes.size(); ind++) + { + BigInteger i = (BigInteger)smallPrimes.elementAt(ind); + BigInteger e = phi_n.divide(i); + + for (;;) + { + tries++; + g = new BigInteger(strength, certainty, rand); + if (g.modPow(e, n).equals(ONE)) + { + continue; + } + gParts.addElement(g); + break; + } + } + g = ONE; + for (int i = 0; i < smallPrimes.size(); i++) + { + g = g.multiply(((BigInteger)gParts.elementAt(i)).modPow(sigma.divide((BigInteger)smallPrimes.elementAt(i)), n)).mod(n); + } + + // make sure that g is not divisible by p_i or q_i + boolean divisible = false; + for (int i = 0; i < smallPrimes.size(); i++) + { + if (g.modPow(phi_n.divide((BigInteger)smallPrimes.elementAt(i)), n).equals(ONE)) + { + if (debug) + { + System.out.println("g has order phi(n)/" + smallPrimes.elementAt(i) + "\n g: " + g); + } + divisible = true; + break; + } + } + + if (divisible) + { + continue; + } + + // make sure that g has order > phi_n/4 + + if (g.modPow(phi_n.divide(BigInteger.valueOf(4)), n).equals(ONE)) + { + if (debug) + { + System.out.println("g has order phi(n)/4\n g:" + g); + } + continue; + } + + if (g.modPow(phi_n.divide(p_), n).equals(ONE)) + { + if (debug) + { + System.out.println("g has order phi(n)/p'\n g: " + g); + } + continue; + } + if (g.modPow(phi_n.divide(q_), n).equals(ONE)) + { + if (debug) + { + System.out.println("g has order phi(n)/q'\n g: " + g); + } + continue; + } + if (g.modPow(phi_n.divide(a), n).equals(ONE)) + { + if (debug) + { + System.out.println("g has order phi(n)/a\n g: " + g); + } + continue; + } + if (g.modPow(phi_n.divide(b), n).equals(ONE)) + { + if (debug) + { + System.out.println("g has order phi(n)/b\n g: " + g); + } + continue; + } + break; + } + if (debug) + { + System.out.println("needed " + tries + " tries to generate g"); + System.out.println(); + System.out.println("found new NaccacheStern cipher variables:"); + System.out.println("smallPrimes: " + smallPrimes); + System.out.println("sigma:...... " + sigma + " (" + sigma.bitLength() + " bits)"); + System.out.println("a:.......... " + a); + System.out.println("b:.......... " + b); + System.out.println("p':......... " + p_); + System.out.println("q':......... " + q_); + System.out.println("p:.......... " + p); + System.out.println("q:.......... " + q); + System.out.println("n:.......... " + n); + System.out.println("phi(n):..... " + phi_n); + System.out.println("g:.......... " + g); + System.out.println(); + } + + return new AsymmetricCipherKeyPair(new NaccacheSternKeyParameters(false, g, n, sigma.bitLength()), + new NaccacheSternPrivateKeyParameters(g, n, sigma.bitLength(), smallPrimes, phi_n)); + } + + private static BigInteger generatePrime( + int bitLength, + int certainty, + SecureRandom rand) + { + BigInteger p_ = new BigInteger(bitLength, certainty, rand); + while (p_.bitLength() != bitLength) + { + p_ = new BigInteger(bitLength, certainty, rand); + } + return p_; + } + + /** + * Generates a permuted ArrayList from the original one. The original List + * is not modified + * + * @param arr + * the ArrayList to be permuted + * @param rand + * the source of Randomness for permutation + * @return a new ArrayList with the permuted elements. + */ + private static Vector permuteList( + Vector arr, + SecureRandom rand) + { + Vector retval = new Vector(); + Vector tmp = new Vector(); + for (int i = 0; i < arr.size(); i++) + { + tmp.addElement(arr.elementAt(i)); + } + retval.addElement(tmp.elementAt(0)); + tmp.removeElementAt(0); + while (tmp.size() != 0) + { + retval.insertElementAt(tmp.elementAt(0), getInt(rand, retval.size() + 1)); + tmp.removeElementAt(0); + } + return retval; + } + + private static int getInt( + SecureRandom rand, + int n) + { + if ((n & -n) == n) + { + return (int)((n * (long)(rand.nextInt() & 0x7fffffff)) >> 31); + } + + int bits, val; + do + { + bits = rand.nextInt() & 0x7fffffff; + val = bits % n; + } + while (bits - val + (n-1) < 0); + + return val; + } + + /** + * Finds the first 'count' primes starting with 3 + * + * @param count + * the number of primes to find + * @return a vector containing the found primes as Integer + */ + private static Vector findFirstPrimes( + int count) + { + Vector primes = new Vector(count); + + for (int i = 0; i != count; i++) + { + primes.addElement(BigInteger.valueOf(smallPrimes[i])); + } + + return primes; + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/OpenSSLPBEParametersGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/OpenSSLPBEParametersGenerator.java new file mode 100644 index 000000000..d54c9685a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/OpenSSLPBEParametersGenerator.java @@ -0,0 +1,131 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.PBEParametersGenerator; +import com.google.bitcoin.bouncycastle.crypto.digests.MD5Digest; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Generator for PBE derived keys and ivs as usd by OpenSSL. + *

    + * The scheme is a simple extension of PKCS 5 V2.0 Scheme 1 using MD5 with an + * iteration count of 1. + *

    + */ +public class OpenSSLPBEParametersGenerator + extends PBEParametersGenerator +{ + private Digest digest = new MD5Digest(); + + /** + * Construct a OpenSSL Parameters generator. + */ + public OpenSSLPBEParametersGenerator() + { + } + + /** + * Initialise - note the iteration count for this algorithm is fixed at 1. + * + * @param password password to use. + * @param salt salt to use. + */ + public void init( + byte[] password, + byte[] salt) + { + super.init(password, salt, 1); + } + + /** + * the derived key function, the ith hash of the password and the salt. + */ + private byte[] generateDerivedKey( + int bytesNeeded) + { + byte[] buf = new byte[digest.getDigestSize()]; + byte[] key = new byte[bytesNeeded]; + int offset = 0; + + for (;;) + { + digest.update(password, 0, password.length); + digest.update(salt, 0, salt.length); + + digest.doFinal(buf, 0); + + int len = (bytesNeeded > buf.length) ? buf.length : bytesNeeded; + System.arraycopy(buf, 0, key, offset, len); + offset += len; + + // check if we need any more + bytesNeeded -= len; + if (bytesNeeded == 0) + { + break; + } + + // do another round + digest.reset(); + digest.update(buf, 0, buf.length); + } + + return key; + } + + /** + * Generate a key parameter derived from the password, salt, and iteration + * count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + * @exception IllegalArgumentException if the key length larger than the base hash size. + */ + public CipherParameters generateDerivedParameters( + int keySize) + { + keySize = keySize / 8; + + byte[] dKey = generateDerivedKey(keySize); + + return new KeyParameter(dKey, 0, keySize); + } + + /** + * Generate a key with initialisation vector parameter derived from + * the password, salt, and iteration count we are currently initialised + * with. + * + * @param keySize the size of the key we want (in bits) + * @param ivSize the size of the iv we want (in bits) + * @return a ParametersWithIV object. + * @exception IllegalArgumentException if keySize + ivSize is larger than the base hash size. + */ + public CipherParameters generateDerivedParameters( + int keySize, + int ivSize) + { + keySize = keySize / 8; + ivSize = ivSize / 8; + + byte[] dKey = generateDerivedKey(keySize + ivSize); + + return new ParametersWithIV(new KeyParameter(dKey, 0, keySize), dKey, keySize, ivSize); + } + + /** + * Generate a key parameter for use with a MAC derived from the password, + * salt, and iteration count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + * @exception IllegalArgumentException if the key length larger than the base hash size. + */ + public CipherParameters generateDerivedMacParameters( + int keySize) + { + return generateDerivedParameters(keySize); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/PKCS12ParametersGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/PKCS12ParametersGenerator.java new file mode 100644 index 000000000..a722de3f0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/PKCS12ParametersGenerator.java @@ -0,0 +1,221 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.ExtendedDigest; +import com.google.bitcoin.bouncycastle.crypto.PBEParametersGenerator; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Generator for PBE derived keys and ivs as defined by PKCS 12 V1.0. + *

    + * The document this implementation is based on can be found at + * + * RSA's PKCS12 Page + */ +public class PKCS12ParametersGenerator + extends PBEParametersGenerator +{ + public static final int KEY_MATERIAL = 1; + public static final int IV_MATERIAL = 2; + public static final int MAC_MATERIAL = 3; + + private Digest digest; + + private int u; + private int v; + + /** + * Construct a PKCS 12 Parameters generator. This constructor will + * accept any digest which also implements ExtendedDigest. + * + * @param digest the digest to be used as the source of derived keys. + * @exception IllegalArgumentException if an unknown digest is passed in. + */ + public PKCS12ParametersGenerator( + Digest digest) + { + this.digest = digest; + if (digest instanceof ExtendedDigest) + { + u = digest.getDigestSize(); + v = ((ExtendedDigest)digest).getByteLength(); + } + else + { + throw new IllegalArgumentException("Digest " + digest.getAlgorithmName() + " unsupported"); + } + } + + /** + * add a + b + 1, returning the result in a. The a value is treated + * as a BigInteger of length (b.length * 8) bits. The result is + * modulo 2^b.length in case of overflow. + */ + private void adjust( + byte[] a, + int aOff, + byte[] b) + { + int x = (b[b.length - 1] & 0xff) + (a[aOff + b.length - 1] & 0xff) + 1; + + a[aOff + b.length - 1] = (byte)x; + x >>>= 8; + + for (int i = b.length - 2; i >= 0; i--) + { + x += (b[i] & 0xff) + (a[aOff + i] & 0xff); + a[aOff + i] = (byte)x; + x >>>= 8; + } + } + + /** + * generation of a derived key ala PKCS12 V1.0. + */ + private byte[] generateDerivedKey( + int idByte, + int n) + { + byte[] D = new byte[v]; + byte[] dKey = new byte[n]; + + for (int i = 0; i != D.length; i++) + { + D[i] = (byte)idByte; + } + + byte[] S; + + if ((salt != null) && (salt.length != 0)) + { + S = new byte[v * ((salt.length + v - 1) / v)]; + + for (int i = 0; i != S.length; i++) + { + S[i] = salt[i % salt.length]; + } + } + else + { + S = new byte[0]; + } + + byte[] P; + + if ((password != null) && (password.length != 0)) + { + P = new byte[v * ((password.length + v - 1) / v)]; + + for (int i = 0; i != P.length; i++) + { + P[i] = password[i % password.length]; + } + } + else + { + P = new byte[0]; + } + + byte[] I = new byte[S.length + P.length]; + + System.arraycopy(S, 0, I, 0, S.length); + System.arraycopy(P, 0, I, S.length, P.length); + + byte[] B = new byte[v]; + int c = (n + u - 1) / u; + + for (int i = 1; i <= c; i++) + { + byte[] A = new byte[u]; + + digest.update(D, 0, D.length); + digest.update(I, 0, I.length); + digest.doFinal(A, 0); + for (int j = 1; j != iterationCount; j++) + { + digest.update(A, 0, A.length); + digest.doFinal(A, 0); + } + + for (int j = 0; j != B.length; j++) + { + B[j] = A[j % A.length]; + } + + for (int j = 0; j != I.length / v; j++) + { + adjust(I, j * v, B); + } + + if (i == c) + { + System.arraycopy(A, 0, dKey, (i - 1) * u, dKey.length - ((i - 1) * u)); + } + else + { + System.arraycopy(A, 0, dKey, (i - 1) * u, A.length); + } + } + + return dKey; + } + + /** + * Generate a key parameter derived from the password, salt, and iteration + * count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + */ + public CipherParameters generateDerivedParameters( + int keySize) + { + keySize = keySize / 8; + + byte[] dKey = generateDerivedKey(KEY_MATERIAL, keySize); + + return new KeyParameter(dKey, 0, keySize); + } + + /** + * Generate a key with initialisation vector parameter derived from + * the password, salt, and iteration count we are currently initialised + * with. + * + * @param keySize the size of the key we want (in bits) + * @param ivSize the size of the iv we want (in bits) + * @return a ParametersWithIV object. + */ + public CipherParameters generateDerivedParameters( + int keySize, + int ivSize) + { + keySize = keySize / 8; + ivSize = ivSize / 8; + + byte[] dKey = generateDerivedKey(KEY_MATERIAL, keySize); + + byte[] iv = generateDerivedKey(IV_MATERIAL, ivSize); + + return new ParametersWithIV(new KeyParameter(dKey, 0, keySize), iv, 0, ivSize); + } + + /** + * Generate a key parameter for use with a MAC derived from the password, + * salt, and iteration count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + */ + public CipherParameters generateDerivedMacParameters( + int keySize) + { + keySize = keySize / 8; + + byte[] dKey = generateDerivedKey(MAC_MATERIAL, keySize); + + return new KeyParameter(dKey, 0, keySize); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/PKCS5S1ParametersGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/PKCS5S1ParametersGenerator.java new file mode 100644 index 000000000..4ed359588 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/PKCS5S1ParametersGenerator.java @@ -0,0 +1,119 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.PBEParametersGenerator; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Generator for PBE derived keys and ivs as defined by PKCS 5 V2.0 Scheme 1. + * Note this generator is limited to the size of the hash produced by the + * digest used to drive it. + *

    + * The document this implementation is based on can be found at + * + * RSA's PKCS5 Page + */ +public class PKCS5S1ParametersGenerator + extends PBEParametersGenerator +{ + private Digest digest; + + /** + * Construct a PKCS 5 Scheme 1 Parameters generator. + * + * @param digest the digest to be used as the source of derived keys. + */ + public PKCS5S1ParametersGenerator( + Digest digest) + { + this.digest = digest; + } + + /** + * the derived key function, the ith hash of the password and the salt. + */ + private byte[] generateDerivedKey() + { + byte[] digestBytes = new byte[digest.getDigestSize()]; + + digest.update(password, 0, password.length); + digest.update(salt, 0, salt.length); + + digest.doFinal(digestBytes, 0); + for (int i = 1; i < iterationCount; i++) + { + digest.update(digestBytes, 0, digestBytes.length); + digest.doFinal(digestBytes, 0); + } + + return digestBytes; + } + + /** + * Generate a key parameter derived from the password, salt, and iteration + * count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + * @exception IllegalArgumentException if the key length larger than the base hash size. + */ + public CipherParameters generateDerivedParameters( + int keySize) + { + keySize = keySize / 8; + + if (keySize > digest.getDigestSize()) + { + throw new IllegalArgumentException( + "Can't generate a derived key " + keySize + " bytes long."); + } + + byte[] dKey = generateDerivedKey(); + + return new KeyParameter(dKey, 0, keySize); + } + + /** + * Generate a key with initialisation vector parameter derived from + * the password, salt, and iteration count we are currently initialised + * with. + * + * @param keySize the size of the key we want (in bits) + * @param ivSize the size of the iv we want (in bits) + * @return a ParametersWithIV object. + * @exception IllegalArgumentException if keySize + ivSize is larger than the base hash size. + */ + public CipherParameters generateDerivedParameters( + int keySize, + int ivSize) + { + keySize = keySize / 8; + ivSize = ivSize / 8; + + if ((keySize + ivSize) > digest.getDigestSize()) + { + throw new IllegalArgumentException( + "Can't generate a derived key " + (keySize + ivSize) + " bytes long."); + } + + byte[] dKey = generateDerivedKey(); + + return new ParametersWithIV(new KeyParameter(dKey, 0, keySize), dKey, keySize, ivSize); + } + + /** + * Generate a key parameter for use with a MAC derived from the password, + * salt, and iteration count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + * @exception IllegalArgumentException if the key length larger than the base hash size. + */ + public CipherParameters generateDerivedMacParameters( + int keySize) + { + return generateDerivedParameters(keySize); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java new file mode 100644 index 000000000..214345abf --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java @@ -0,0 +1,151 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.Mac; +import com.google.bitcoin.bouncycastle.crypto.PBEParametersGenerator; +import com.google.bitcoin.bouncycastle.crypto.digests.SHA1Digest; +import com.google.bitcoin.bouncycastle.crypto.macs.HMac; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Generator for PBE derived keys and ivs as defined by PKCS 5 V2.0 Scheme 2. + * This generator uses a SHA-1 HMac as the calculation function. + *

    + * The document this implementation is based on can be found at + * + * RSA's PKCS5 Page + */ +public class PKCS5S2ParametersGenerator + extends PBEParametersGenerator +{ + private Mac hMac = new HMac(new SHA1Digest()); + + /** + * construct a PKCS5 Scheme 2 Parameters generator. + */ + public PKCS5S2ParametersGenerator() + { + } + + private void F( + byte[] P, + byte[] S, + int c, + byte[] iBuf, + byte[] out, + int outOff) + { + byte[] state = new byte[hMac.getMacSize()]; + CipherParameters param = new KeyParameter(P); + + hMac.init(param); + + if (S != null) + { + hMac.update(S, 0, S.length); + } + + hMac.update(iBuf, 0, iBuf.length); + + hMac.doFinal(state, 0); + + System.arraycopy(state, 0, out, outOff, state.length); + + if (c == 0) + { + throw new IllegalArgumentException("iteration count must be at least 1."); + } + + for (int count = 1; count < c; count++) + { + hMac.init(param); + hMac.update(state, 0, state.length); + hMac.doFinal(state, 0); + + for (int j = 0; j != state.length; j++) + { + out[outOff + j] ^= state[j]; + } + } + } + + private void intToOctet( + byte[] buf, + int i) + { + buf[0] = (byte)(i >>> 24); + buf[1] = (byte)(i >>> 16); + buf[2] = (byte)(i >>> 8); + buf[3] = (byte)i; + } + + private byte[] generateDerivedKey( + int dkLen) + { + int hLen = hMac.getMacSize(); + int l = (dkLen + hLen - 1) / hLen; + byte[] iBuf = new byte[4]; + byte[] out = new byte[l * hLen]; + + for (int i = 1; i <= l; i++) + { + intToOctet(iBuf, i); + + F(password, salt, iterationCount, iBuf, out, (i - 1) * hLen); + } + + return out; + } + + /** + * Generate a key parameter derived from the password, salt, and iteration + * count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + */ + public CipherParameters generateDerivedParameters( + int keySize) + { + keySize = keySize / 8; + + byte[] dKey = generateDerivedKey(keySize); + + return new KeyParameter(dKey, 0, keySize); + } + + /** + * Generate a key with initialisation vector parameter derived from + * the password, salt, and iteration count we are currently initialised + * with. + * + * @param keySize the size of the key we want (in bits) + * @param ivSize the size of the iv we want (in bits) + * @return a ParametersWithIV object. + */ + public CipherParameters generateDerivedParameters( + int keySize, + int ivSize) + { + keySize = keySize / 8; + ivSize = ivSize / 8; + + byte[] dKey = generateDerivedKey(keySize + ivSize); + + return new ParametersWithIV(new KeyParameter(dKey, 0, keySize), dKey, keySize, ivSize); + } + + /** + * Generate a key parameter for use with a MAC derived from the password, + * salt, and iteration count we are currently initialised with. + * + * @param keySize the size of the key we want (in bits) + * @return a KeyParameter object. + */ + public CipherParameters generateDerivedMacParameters( + int keySize) + { + return generateDerivedParameters(keySize); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/RSABlindingFactorGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/RSABlindingFactorGenerator.java new file mode 100644 index 000000000..fc4ab7bf8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/RSABlindingFactorGenerator.java @@ -0,0 +1,77 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.crypto.params.RSAKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * Generate a random factor suitable for use with RSA blind signatures + * as outlined in Chaum's blinding and unblinding as outlined in + * "Handbook of Applied Cryptography", page 475. + */ +public class RSABlindingFactorGenerator +{ + private static BigInteger ZERO = BigInteger.valueOf(0); + private static BigInteger ONE = BigInteger.valueOf(1); + + private RSAKeyParameters key; + private SecureRandom random; + + /** + * Initialise the factor generator + * + * @param param the necessary RSA key parameters. + */ + public void init( + CipherParameters param) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + key = (RSAKeyParameters)rParam.getParameters(); + random = rParam.getRandom(); + } + else + { + key = (RSAKeyParameters)param; + random = new SecureRandom(); + } + + if (key instanceof RSAPrivateCrtKeyParameters) + { + throw new IllegalArgumentException("generator requires RSA public key"); + } + } + + /** + * Generate a suitable blind factor for the public key the generator was initialised with. + * + * @return a random blind factor + */ + public BigInteger generateBlindingFactor() + { + if (key == null) + { + throw new IllegalStateException("generator not initialised"); + } + + BigInteger m = key.getModulus(); + int length = m.bitLength() - 1; // must be less than m.bitLength() + BigInteger factor; + BigInteger gcd; + + do + { + factor = new BigInteger(length, random); + gcd = factor.gcd(m); + } + while (factor.equals(ZERO) || factor.equals(ONE) || !gcd.equals(ONE)); + + return factor; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/generators/RSAKeyPairGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/generators/RSAKeyPairGenerator.java new file mode 100644 index 000000000..ad646ba4a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/generators/RSAKeyPairGenerator.java @@ -0,0 +1,147 @@ +package com.google.bitcoin.bouncycastle.crypto.generators; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPair; +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.RSAKeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.RSAKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; + +import java.math.BigInteger; + +/** + * an RSA key pair generator. + */ +public class RSAKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + private static final BigInteger ONE = BigInteger.valueOf(1); + + private RSAKeyGenerationParameters param; + + public void init( + KeyGenerationParameters param) + { + this.param = (RSAKeyGenerationParameters)param; + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + BigInteger p, q, n, d, e, pSub1, qSub1, phi; + + // + // p and q values should have a length of half the strength in bits + // + int strength = param.getStrength(); + int pbitlength = (strength + 1) / 2; + int qbitlength = strength - pbitlength; + int mindiffbits = strength / 3; + + e = param.getPublicExponent(); + + // TODO Consider generating safe primes for p, q (see DHParametersHelper.generateSafePrimes) + // (then p-1 and q-1 will not consist of only small factors - see "Pollard's algorithm") + + // + // generate p, prime and (p-1) relatively prime to e + // + for (;;) + { + p = new BigInteger(pbitlength, 1, param.getRandom()); + + if (p.mod(e).equals(ONE)) + { + continue; + } + + if (!p.isProbablePrime(param.getCertainty())) + { + continue; + } + + if (e.gcd(p.subtract(ONE)).equals(ONE)) + { + break; + } + } + + // + // generate a modulus of the required length + // + for (;;) + { + // generate q, prime and (q-1) relatively prime to e, + // and not equal to p + // + for (;;) + { + q = new BigInteger(qbitlength, 1, param.getRandom()); + + if (q.subtract(p).abs().bitLength() < mindiffbits) + { + continue; + } + + if (q.mod(e).equals(ONE)) + { + continue; + } + + if (!q.isProbablePrime(param.getCertainty())) + { + continue; + } + + if (e.gcd(q.subtract(ONE)).equals(ONE)) + { + break; + } + } + + // + // calculate the modulus + // + n = p.multiply(q); + + if (n.bitLength() == param.getStrength()) + { + break; + } + + // + // if we get here our primes aren't big enough, make the largest + // of the two p and try again + // + p = p.max(q); + } + + if (p.compareTo(q) < 0) + { + phi = p; + p = q; + q = phi; + } + + pSub1 = p.subtract(ONE); + qSub1 = q.subtract(ONE); + phi = pSub1.multiply(qSub1); + + // + // calculate the private exponent + // + d = e.modInverse(phi); + + // + // calculate the CRT factors + // + BigInteger dP, dQ, qInv; + + dP = d.remainder(pSub1); + dQ = d.remainder(qSub1); + qInv = q.modInverse(p); + + return new AsymmetricCipherKeyPair( + new RSAKeyParameters(false, n, e), + new RSAPrivateCrtKeyParameters(n, e, d, p, q, dP, dQ, qInv)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/io/DigestInputStream.java b/src/com/google/bitcoin/bouncycastle/crypto/io/DigestInputStream.java new file mode 100644 index 000000000..c51a58cbf --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/io/DigestInputStream.java @@ -0,0 +1,52 @@ +package com.google.bitcoin.bouncycastle.crypto.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import com.google.bitcoin.bouncycastle.crypto.Digest; + +public class DigestInputStream + extends FilterInputStream +{ + protected Digest digest; + + public DigestInputStream( + InputStream stream, + Digest digest) + { + super(stream); + this.digest = digest; + } + + public int read() + throws IOException + { + int b = in.read(); + + if (b >= 0) + { + digest.update((byte)b); + } + return b; + } + + public int read( + byte[] b, + int off, + int len) + throws IOException + { + int n = in.read(b, off, len); + if (n > 0) + { + digest.update(b, off, n); + } + return n; + } + + public Digest getDigest() + { + return digest; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/io/DigestOutputStream.java b/src/com/google/bitcoin/bouncycastle/crypto/io/DigestOutputStream.java new file mode 100644 index 000000000..72f1c3638 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/io/DigestOutputStream.java @@ -0,0 +1,43 @@ +package com.google.bitcoin.bouncycastle.crypto.io; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import com.google.bitcoin.bouncycastle.crypto.Digest; + +public class DigestOutputStream + extends FilterOutputStream +{ + protected Digest digest; + + public DigestOutputStream( + OutputStream stream, + Digest digest) + { + super(stream); + this.digest = digest; + } + + public void write(int b) + throws IOException + { + digest.update((byte)b); + out.write(b); + } + + public void write( + byte[] b, + int off, + int len) + throws IOException + { + digest.update(b, off, len); + out.write(b, off, len); + } + + public Digest getDigest() + { + return digest; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/io/MacInputStream.java b/src/com/google/bitcoin/bouncycastle/crypto/io/MacInputStream.java new file mode 100644 index 000000000..423e523ed --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/io/MacInputStream.java @@ -0,0 +1,52 @@ +package com.google.bitcoin.bouncycastle.crypto.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import com.google.bitcoin.bouncycastle.crypto.Mac; + +public class MacInputStream + extends FilterInputStream +{ + protected Mac mac; + + public MacInputStream( + InputStream stream, + Mac mac) + { + super(stream); + this.mac = mac; + } + + public int read() + throws IOException + { + int b = in.read(); + + if (b >= 0) + { + mac.update((byte)b); + } + return b; + } + + public int read( + byte[] b, + int off, + int len) + throws IOException + { + int n = in.read(b, off, len); + if (n >= 0) + { + mac.update(b, off, n); + } + return n; + } + + public Mac getMac() + { + return mac; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/io/MacOutputStream.java b/src/com/google/bitcoin/bouncycastle/crypto/io/MacOutputStream.java new file mode 100644 index 000000000..c28e4b107 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/io/MacOutputStream.java @@ -0,0 +1,44 @@ +package com.google.bitcoin.bouncycastle.crypto.io; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import com.google.bitcoin.bouncycastle.crypto.Mac; + +public class MacOutputStream + extends FilterOutputStream +{ + protected Mac mac; + + public MacOutputStream( + OutputStream stream, + Mac mac) + { + super(stream); + this.mac = mac; + } + + public void write(int b) + throws IOException + { + mac.update((byte)b); + out.write(b); + } + + public void write( + byte[] b, + int off, + int len) + throws IOException + { + mac.update(b, off, len); + out.write(b, off, len); + } + + public Mac getMac() + { + return mac; + } +} + diff --git a/src/com/google/bitcoin/bouncycastle/crypto/io/SignerInputStream.java b/src/com/google/bitcoin/bouncycastle/crypto/io/SignerInputStream.java new file mode 100644 index 000000000..3929e4ab1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/io/SignerInputStream.java @@ -0,0 +1,52 @@ +package com.google.bitcoin.bouncycastle.crypto.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import com.google.bitcoin.bouncycastle.crypto.Signer; + +public class SignerInputStream + extends FilterInputStream +{ + protected Signer signer; + + public SignerInputStream( + InputStream stream, + Signer signer) + { + super(stream); + this.signer = signer; + } + + public int read() + throws IOException + { + int b = in.read(); + + if (b >= 0) + { + signer.update((byte)b); + } + return b; + } + + public int read( + byte[] b, + int off, + int len) + throws IOException + { + int n = in.read(b, off, len); + if (n > 0) + { + signer.update(b, off, n); + } + return n; + } + + public Signer getSigner() + { + return signer; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/io/SignerOutputStream.java b/src/com/google/bitcoin/bouncycastle/crypto/io/SignerOutputStream.java new file mode 100644 index 000000000..60bb09d9c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/io/SignerOutputStream.java @@ -0,0 +1,43 @@ +package com.google.bitcoin.bouncycastle.crypto.io; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import com.google.bitcoin.bouncycastle.crypto.Signer; + +public class SignerOutputStream + extends FilterOutputStream +{ + protected Signer signer; + + public SignerOutputStream( + OutputStream stream, + Signer signer) + { + super(stream); + this.signer = signer; + } + + public void write(int b) + throws IOException + { + signer.update((byte)b); + out.write(b); + } + + public void write( + byte[] b, + int off, + int len) + throws IOException + { + signer.update(b, off, len); + out.write(b, off, len); + } + + public Signer getSigner() + { + return signer; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/macs/BlockCipherMac.java b/src/com/google/bitcoin/bouncycastle/crypto/macs/BlockCipherMac.java new file mode 100644 index 000000000..de41bde91 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/macs/BlockCipherMac.java @@ -0,0 +1,174 @@ +package com.google.bitcoin.bouncycastle.crypto.macs; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.Mac; +import com.google.bitcoin.bouncycastle.crypto.modes.CBCBlockCipher; + +public class BlockCipherMac + implements Mac +{ + private byte[] mac; + + private byte[] buf; + private int bufOff; + private BlockCipher cipher; + + private int macSize; + + /** + * create a standard MAC based on a block cipher. This will produce an + * authentication code half the length of the block size of the cipher. + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @deprecated use CBCBlockCipherMac + */ + public BlockCipherMac( + BlockCipher cipher) + { + this(cipher, (cipher.getBlockSize() * 8) / 2); + } + + /** + * create a standard MAC based on a block cipher with the size of the + * MAC been given in bits. + *

    + * Note: the size of the MAC must be at least 16 bits (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + * @deprecated use CBCBlockCipherMac + */ + public BlockCipherMac( + BlockCipher cipher, + int macSizeInBits) + { + if ((macSizeInBits % 8) != 0) + { + throw new IllegalArgumentException("MAC size must be multiple of 8"); + } + + this.cipher = new CBCBlockCipher(cipher); + this.macSize = macSizeInBits / 8; + + mac = new byte[cipher.getBlockSize()]; + + buf = new byte[cipher.getBlockSize()]; + bufOff = 0; + } + + public String getAlgorithmName() + { + return cipher.getAlgorithmName(); + } + + public void init( + CipherParameters params) + { + reset(); + + cipher.init(true, params); + } + + public int getMacSize() + { + return macSize; + } + + public void update( + byte in) + { + if (bufOff == buf.length) + { + cipher.processBlock(buf, 0, mac, 0); + bufOff = 0; + } + + buf[bufOff++] = in; + } + + public void update( + byte[] in, + int inOff, + int len) + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = cipher.getBlockSize(); + int resultLen = 0; + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += cipher.processBlock(buf, 0, mac, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + resultLen += cipher.processBlock(in, inOff, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int doFinal( + byte[] out, + int outOff) + { + int blockSize = cipher.getBlockSize(); + + // + // pad with zeroes + // + while (bufOff < blockSize) + { + buf[bufOff] = 0; + bufOff++; + } + + cipher.processBlock(buf, 0, mac, 0); + + System.arraycopy(mac, 0, out, outOff, macSize); + + reset(); + + return macSize; + } + + /** + * Reset the mac generator. + */ + public void reset() + { + /* + * clean the buffer. + */ + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + + bufOff = 0; + + /* + * reset the underlying cipher. + */ + cipher.reset(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/macs/CBCBlockCipherMac.java b/src/com/google/bitcoin/bouncycastle/crypto/macs/CBCBlockCipherMac.java new file mode 100644 index 000000000..94383f9ff --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/macs/CBCBlockCipherMac.java @@ -0,0 +1,229 @@ +package com.google.bitcoin.bouncycastle.crypto.macs; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.Mac; +import com.google.bitcoin.bouncycastle.crypto.modes.CBCBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.paddings.BlockCipherPadding; + +/** + * standard CBC Block Cipher MAC - if no padding is specified the default of + * pad of zeroes is used. + */ +public class CBCBlockCipherMac + implements Mac +{ + private byte[] mac; + + private byte[] buf; + private int bufOff; + private BlockCipher cipher; + private BlockCipherPadding padding; + + private int macSize; + + /** + * create a standard MAC based on a CBC block cipher. This will produce an + * authentication code half the length of the block size of the cipher. + * + * @param cipher the cipher to be used as the basis of the MAC generation. + */ + public CBCBlockCipherMac( + BlockCipher cipher) + { + this(cipher, (cipher.getBlockSize() * 8) / 2, null); + } + + /** + * create a standard MAC based on a CBC block cipher. This will produce an + * authentication code half the length of the block size of the cipher. + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param padding the padding to be used to complete the last block. + */ + public CBCBlockCipherMac( + BlockCipher cipher, + BlockCipherPadding padding) + { + this(cipher, (cipher.getBlockSize() * 8) / 2, padding); + } + + /** + * create a standard MAC based on a block cipher with the size of the + * MAC been given in bits. This class uses CBC mode as the basis for the + * MAC generation. + *

    + * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + * or 16 bits if being used as a data authenticator (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + */ + public CBCBlockCipherMac( + BlockCipher cipher, + int macSizeInBits) + { + this(cipher, macSizeInBits, null); + } + + /** + * create a standard MAC based on a block cipher with the size of the + * MAC been given in bits. This class uses CBC mode as the basis for the + * MAC generation. + *

    + * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + * or 16 bits if being used as a data authenticator (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + * @param padding the padding to be used to complete the last block. + */ + public CBCBlockCipherMac( + BlockCipher cipher, + int macSizeInBits, + BlockCipherPadding padding) + { + if ((macSizeInBits % 8) != 0) + { + throw new IllegalArgumentException("MAC size must be multiple of 8"); + } + + this.cipher = new CBCBlockCipher(cipher); + this.padding = padding; + this.macSize = macSizeInBits / 8; + + mac = new byte[cipher.getBlockSize()]; + + buf = new byte[cipher.getBlockSize()]; + bufOff = 0; + } + + public String getAlgorithmName() + { + return cipher.getAlgorithmName(); + } + + public void init( + CipherParameters params) + { + reset(); + + cipher.init(true, params); + } + + public int getMacSize() + { + return macSize; + } + + public void update( + byte in) + { + if (bufOff == buf.length) + { + cipher.processBlock(buf, 0, mac, 0); + bufOff = 0; + } + + buf[bufOff++] = in; + } + + public void update( + byte[] in, + int inOff, + int len) + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = cipher.getBlockSize(); + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + cipher.processBlock(buf, 0, mac, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + cipher.processBlock(in, inOff, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int doFinal( + byte[] out, + int outOff) + { + int blockSize = cipher.getBlockSize(); + + if (padding == null) + { + // + // pad with zeroes + // + while (bufOff < blockSize) + { + buf[bufOff] = 0; + bufOff++; + } + } + else + { + if (bufOff == blockSize) + { + cipher.processBlock(buf, 0, mac, 0); + bufOff = 0; + } + + padding.addPadding(buf, bufOff); + } + + cipher.processBlock(buf, 0, mac, 0); + + System.arraycopy(mac, 0, out, outOff, macSize); + + reset(); + + return macSize; + } + + /** + * Reset the mac generator. + */ + public void reset() + { + /* + * clean the buffer. + */ + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + + bufOff = 0; + + /* + * reset the underlying cipher. + */ + cipher.reset(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/macs/CFBBlockCipherMac.java b/src/com/google/bitcoin/bouncycastle/crypto/macs/CFBBlockCipherMac.java new file mode 100644 index 000000000..08720fef8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/macs/CFBBlockCipherMac.java @@ -0,0 +1,388 @@ +package com.google.bitcoin.bouncycastle.crypto.macs; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.Mac; +import com.google.bitcoin.bouncycastle.crypto.paddings.BlockCipherPadding; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +/** + * implements a Cipher-FeedBack (CFB) mode on top of a simple cipher. + */ +class MacCFBBlockCipher +{ + private byte[] IV; + private byte[] cfbV; + private byte[] cfbOutV; + + private int blockSize; + private BlockCipher cipher = null; + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of the + * feedback mode. + * @param blockSize the block size in bits (note: a multiple of 8) + */ + public MacCFBBlockCipher( + BlockCipher cipher, + int bitBlockSize) + { + this.cipher = cipher; + this.blockSize = bitBlockSize / 8; + + this.IV = new byte[cipher.getBlockSize()]; + this.cfbV = new byte[cipher.getBlockSize()]; + this.cfbOutV = new byte[cipher.getBlockSize()]; + } + + /** + * Initialise the cipher and, possibly, the initialisation vector (IV). + * If an IV isn't passed as part of the parameter, the IV will be all zeros. + * An IV which is too short is handled in FIPS compliant fashion. + * + * @param param the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + CipherParameters params) + throws IllegalArgumentException + { + if (params instanceof ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)params; + byte[] iv = ivParam.getIV(); + + if (iv.length < IV.length) + { + System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length); + } + else + { + System.arraycopy(iv, 0, IV, 0, IV.length); + } + + reset(); + + cipher.init(true, ivParam.getParameters()); + } + else + { + reset(); + + cipher.init(true, params); + } + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/CFB" + * and the block size in bits. + */ + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/CFB" + (blockSize * 8); + } + + /** + * return the block size we are operating at. + * + * @return the block size we are operating at (in bytes). + */ + public int getBlockSize() + { + return blockSize; + } + + /** + * Process one block of input from the array in and write it to + * the out array. + * + * @param in the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param out the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + cipher.processBlock(cfbV, 0, cfbOutV, 0); + + // + // XOR the cfbV with the plaintext producing the cipher text + // + for (int i = 0; i < blockSize; i++) + { + out[outOff + i] = (byte)(cfbOutV[i] ^ in[inOff + i]); + } + + // + // change over the input block. + // + System.arraycopy(cfbV, blockSize, cfbV, 0, cfbV.length - blockSize); + System.arraycopy(out, outOff, cfbV, cfbV.length - blockSize, blockSize); + + return blockSize; + } + + /** + * reset the chaining vector back to the IV and reset the underlying + * cipher. + */ + public void reset() + { + System.arraycopy(IV, 0, cfbV, 0, IV.length); + + cipher.reset(); + } + + void getMacBlock( + byte[] mac) + { + cipher.processBlock(cfbV, 0, mac, 0); + } +} + +public class CFBBlockCipherMac + implements Mac +{ + private byte[] mac; + + private byte[] buf; + private int bufOff; + private MacCFBBlockCipher cipher; + private BlockCipherPadding padding = null; + + + private int macSize; + + /** + * create a standard MAC based on a CFB block cipher. This will produce an + * authentication code half the length of the block size of the cipher, with + * the CFB mode set to 8 bits. + * + * @param cipher the cipher to be used as the basis of the MAC generation. + */ + public CFBBlockCipherMac( + BlockCipher cipher) + { + this(cipher, 8, (cipher.getBlockSize() * 8) / 2, null); + } + + /** + * create a standard MAC based on a CFB block cipher. This will produce an + * authentication code half the length of the block size of the cipher, with + * the CFB mode set to 8 bits. + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param padding the padding to be used. + */ + public CFBBlockCipherMac( + BlockCipher cipher, + BlockCipherPadding padding) + { + this(cipher, 8, (cipher.getBlockSize() * 8) / 2, padding); + } + + /** + * create a standard MAC based on a block cipher with the size of the + * MAC been given in bits. This class uses CFB mode as the basis for the + * MAC generation. + *

    + * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + * or 16 bits if being used as a data authenticator (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param cfbBitSize the size of an output block produced by the CFB mode. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + */ + public CFBBlockCipherMac( + BlockCipher cipher, + int cfbBitSize, + int macSizeInBits) + { + this(cipher, cfbBitSize, macSizeInBits, null); + } + + /** + * create a standard MAC based on a block cipher with the size of the + * MAC been given in bits. This class uses CFB mode as the basis for the + * MAC generation. + *

    + * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + * or 16 bits if being used as a data authenticator (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param cfbBitSize the size of an output block produced by the CFB mode. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + * @param padding a padding to be used. + */ + public CFBBlockCipherMac( + BlockCipher cipher, + int cfbBitSize, + int macSizeInBits, + BlockCipherPadding padding) + { + if ((macSizeInBits % 8) != 0) + { + throw new IllegalArgumentException("MAC size must be multiple of 8"); + } + + mac = new byte[cipher.getBlockSize()]; + + this.cipher = new MacCFBBlockCipher(cipher, cfbBitSize); + this.padding = padding; + this.macSize = macSizeInBits / 8; + + buf = new byte[this.cipher.getBlockSize()]; + bufOff = 0; + } + + public String getAlgorithmName() + { + return cipher.getAlgorithmName(); + } + + public void init( + CipherParameters params) + { + reset(); + + cipher.init(params); + } + + public int getMacSize() + { + return macSize; + } + + public void update( + byte in) + { + if (bufOff == buf.length) + { + cipher.processBlock(buf, 0, mac, 0); + bufOff = 0; + } + + buf[bufOff++] = in; + } + + public void update( + byte[] in, + int inOff, + int len) + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = cipher.getBlockSize(); + int resultLen = 0; + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += cipher.processBlock(buf, 0, mac, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + resultLen += cipher.processBlock(in, inOff, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int doFinal( + byte[] out, + int outOff) + { + int blockSize = cipher.getBlockSize(); + + // + // pad with zeroes + // + if (this.padding == null) + { + while (bufOff < blockSize) + { + buf[bufOff] = 0; + bufOff++; + } + } + else + { + padding.addPadding(buf, bufOff); + } + + cipher.processBlock(buf, 0, mac, 0); + + cipher.getMacBlock(mac); + + System.arraycopy(mac, 0, out, outOff, macSize); + + reset(); + + return macSize; + } + + /** + * Reset the mac generator. + */ + public void reset() + { + /* + * clean the buffer. + */ + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + + bufOff = 0; + + /* + * reset the underlying cipher. + */ + cipher.reset(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/macs/CMac.java b/src/com/google/bitcoin/bouncycastle/crypto/macs/CMac.java new file mode 100644 index 000000000..e37b58c27 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/macs/CMac.java @@ -0,0 +1,237 @@ +package com.google.bitcoin.bouncycastle.crypto.macs; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.Mac; +import com.google.bitcoin.bouncycastle.crypto.modes.CBCBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.paddings.ISO7816d4Padding; + +/** + * CMAC - as specified at www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/omac.html + *

    + * CMAC is analogous to OMAC1 - see also en.wikipedia.org/wiki/CMAC + *

    + * CMAC is a NIST recomendation - see + * csrc.nist.gov/CryptoToolkit/modes/800-38_Series_Publications/SP800-38B.pdf + *

    + * CMAC/OMAC1 is a blockcipher-based message authentication code designed and + * analyzed by Tetsu Iwata and Kaoru Kurosawa. + *

    + * CMAC/OMAC1 is a simple variant of the CBC MAC (Cipher Block Chaining Message + * Authentication Code). OMAC stands for One-Key CBC MAC. + *

    + * It supports 128- or 64-bits block ciphers, with any key size, and returns + * a MAC with dimension less or equal to the block size of the underlying + * cipher. + *

    + */ +public class CMac implements Mac +{ + private static final byte CONSTANT_128 = (byte)0x87; + private static final byte CONSTANT_64 = (byte)0x1b; + + private byte[] ZEROES; + + private byte[] mac; + + private byte[] buf; + private int bufOff; + private BlockCipher cipher; + + private int macSize; + + private byte[] L, Lu, Lu2; + + /** + * create a standard MAC based on a CBC block cipher (64 or 128 bit block). + * This will produce an authentication code the length of the block size + * of the cipher. + * + * @param cipher the cipher to be used as the basis of the MAC generation. + */ + public CMac(BlockCipher cipher) + { + this(cipher, cipher.getBlockSize() * 8); + } + + /** + * create a standard MAC based on a block cipher with the size of the + * MAC been given in bits. + *

    + * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + * or 16 bits if being used as a data authenticator (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8 and <= 128. + */ + public CMac(BlockCipher cipher, int macSizeInBits) + { + if ((macSizeInBits % 8) != 0) + { + throw new IllegalArgumentException("MAC size must be multiple of 8"); + } + + if (macSizeInBits > (cipher.getBlockSize() * 8)) + { + throw new IllegalArgumentException( + "MAC size must be less or equal to " + + (cipher.getBlockSize() * 8)); + } + + if (cipher.getBlockSize() != 8 && cipher.getBlockSize() != 16) + { + throw new IllegalArgumentException( + "Block size must be either 64 or 128 bits"); + } + + this.cipher = new CBCBlockCipher(cipher); + this.macSize = macSizeInBits / 8; + + mac = new byte[cipher.getBlockSize()]; + + buf = new byte[cipher.getBlockSize()]; + + ZEROES = new byte[cipher.getBlockSize()]; + + bufOff = 0; + } + + public String getAlgorithmName() + { + return cipher.getAlgorithmName(); + } + + private byte[] doubleLu(byte[] in) + { + int FirstBit = (in[0] & 0xFF) >> 7; + byte[] ret = new byte[in.length]; + for (int i = 0; i < in.length - 1; i++) + { + ret[i] = (byte)((in[i] << 1) + ((in[i + 1] & 0xFF) >> 7)); + } + ret[in.length - 1] = (byte)(in[in.length - 1] << 1); + if (FirstBit == 1) + { + ret[in.length - 1] ^= in.length == 16 ? CONSTANT_128 : CONSTANT_64; + } + return ret; + } + + public void init(CipherParameters params) + { + reset(); + + cipher.init(true, params); + + //initializes the L, Lu, Lu2 numbers + L = new byte[ZEROES.length]; + cipher.processBlock(ZEROES, 0, L, 0); + Lu = doubleLu(L); + Lu2 = doubleLu(Lu); + + cipher.init(true, params); + } + + public int getMacSize() + { + return macSize; + } + + public void update(byte in) + { + if (bufOff == buf.length) + { + cipher.processBlock(buf, 0, mac, 0); + bufOff = 0; + } + + buf[bufOff++] = in; + } + + public void update(byte[] in, int inOff, int len) + { + if (len < 0) + { + throw new IllegalArgumentException( + "Can't have a negative input length!"); + } + + int blockSize = cipher.getBlockSize(); + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + cipher.processBlock(buf, 0, mac, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + cipher.processBlock(in, inOff, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int doFinal(byte[] out, int outOff) + { + int blockSize = cipher.getBlockSize(); + + byte[] lu; + if (bufOff == blockSize) + { + lu = Lu; + } + else + { + new ISO7816d4Padding().addPadding(buf, bufOff); + lu = Lu2; + } + + for (int i = 0; i < mac.length; i++) + { + buf[i] ^= lu[i]; + } + + cipher.processBlock(buf, 0, mac, 0); + + System.arraycopy(mac, 0, out, outOff, macSize); + + reset(); + + return macSize; + } + + /** + * Reset the mac generator. + */ + public void reset() + { + /* + * clean the buffer. + */ + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + + bufOff = 0; + + /* + * reset the underlying cipher. + */ + cipher.reset(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/macs/GOST28147Mac.java b/src/com/google/bitcoin/bouncycastle/crypto/macs/GOST28147Mac.java new file mode 100644 index 000000000..e5518cbff --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/macs/GOST28147Mac.java @@ -0,0 +1,298 @@ +package com.google.bitcoin.bouncycastle.crypto.macs; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.Mac; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithSBox; + +/** + * implementation of GOST 28147-89 MAC + */ +public class GOST28147Mac + implements Mac +{ + private int blockSize = 8; + private int macSize = 4; + private int bufOff; + private byte[] buf; + private byte[] mac; + private boolean firstStep = true; + private int[] workingKey = null; + + // + // This is default S-box - E_A. + private byte S[] = { + 0x9,0x6,0x3,0x2,0x8,0xB,0x1,0x7,0xA,0x4,0xE,0xF,0xC,0x0,0xD,0x5, + 0x3,0x7,0xE,0x9,0x8,0xA,0xF,0x0,0x5,0x2,0x6,0xC,0xB,0x4,0xD,0x1, + 0xE,0x4,0x6,0x2,0xB,0x3,0xD,0x8,0xC,0xF,0x5,0xA,0x0,0x7,0x1,0x9, + 0xE,0x7,0xA,0xC,0xD,0x1,0x3,0x9,0x0,0x2,0xB,0x4,0xF,0x8,0x5,0x6, + 0xB,0x5,0x1,0x9,0x8,0xD,0xF,0x0,0xE,0x4,0x2,0x3,0xC,0x7,0xA,0x6, + 0x3,0xA,0xD,0xC,0x1,0x2,0x0,0xB,0x7,0x5,0x9,0x4,0x8,0xF,0xE,0x6, + 0x1,0xD,0x2,0x9,0x7,0xA,0x6,0x0,0x8,0xC,0x4,0x5,0xF,0x3,0xB,0xE, + 0xB,0xA,0xF,0x5,0x0,0xC,0xE,0x8,0x6,0x2,0x3,0x9,0x1,0x7,0xD,0x4 + }; + + public GOST28147Mac() + { + mac = new byte[blockSize]; + + buf = new byte[blockSize]; + bufOff = 0; + } + + private int[] generateWorkingKey( + byte[] userKey) + { + if (userKey.length != 32) + { + throw new IllegalArgumentException("Key length invalid. Key needs to be 32 byte - 256 bit!!!"); + } + + int key[] = new int[8]; + for(int i=0; i!=8; i++) + { + key[i] = bytesToint(userKey,i*4); + } + + return key; + } + + public void init( + CipherParameters params) + throws IllegalArgumentException + { + reset(); + buf = new byte[blockSize]; + if (params instanceof ParametersWithSBox) + { + ParametersWithSBox param = (ParametersWithSBox)params; + + // + // Set the S-Box + // + System.arraycopy(param.getSBox(), 0, this.S, 0, param.getSBox().length); + + // + // set key if there is one + // + if (param.getParameters() != null) + { + workingKey = generateWorkingKey(((KeyParameter)param.getParameters()).getKey()); + } + } + else if (params instanceof KeyParameter) + { + workingKey = generateWorkingKey(((KeyParameter)params).getKey()); + } + else + { + throw new IllegalArgumentException("invalid parameter passed to GOST28147 init - " + params.getClass().getName()); + } + } + + public String getAlgorithmName() + { + return "GOST28147Mac"; + } + + public int getMacSize() + { + return macSize; + } + + private int gost28147_mainStep(int n1, int key) + { + int cm = (key + n1); // CM1 + + // S-box replacing + + int om = S[ 0 + ((cm >> (0 * 4)) & 0xF)] << (0 * 4); + om += S[ 16 + ((cm >> (1 * 4)) & 0xF)] << (1 * 4); + om += S[ 32 + ((cm >> (2 * 4)) & 0xF)] << (2 * 4); + om += S[ 48 + ((cm >> (3 * 4)) & 0xF)] << (3 * 4); + om += S[ 64 + ((cm >> (4 * 4)) & 0xF)] << (4 * 4); + om += S[ 80 + ((cm >> (5 * 4)) & 0xF)] << (5 * 4); + om += S[ 96 + ((cm >> (6 * 4)) & 0xF)] << (6 * 4); + om += S[112 + ((cm >> (7 * 4)) & 0xF)] << (7 * 4); + + return om << 11 | om >>> (32-11); // 11-leftshift + } + + private void gost28147MacFunc( + int[] workingKey, + byte[] in, + int inOff, + byte[] out, + int outOff) + { + int N1, N2, tmp; //tmp -> for saving N1 + N1 = bytesToint(in, inOff); + N2 = bytesToint(in, inOff + 4); + + for(int k = 0; k < 2; k++) // 1-16 steps + { + for(int j = 0; j < 8; j++) + { + tmp = N1; + N1 = N2 ^ gost28147_mainStep(N1, workingKey[j]); // CM2 + N2 = tmp; + } + } + + intTobytes(N1, out, outOff); + intTobytes(N2, out, outOff + 4); + } + + //array of bytes to type int + private int bytesToint( + byte[] in, + int inOff) + { + return ((in[inOff + 3] << 24) & 0xff000000) + ((in[inOff + 2] << 16) & 0xff0000) + + ((in[inOff + 1] << 8) & 0xff00) + (in[inOff] & 0xff); + } + + //int to array of bytes + private void intTobytes( + int num, + byte[] out, + int outOff) + { + out[outOff + 3] = (byte)(num >>> 24); + out[outOff + 2] = (byte)(num >>> 16); + out[outOff + 1] = (byte)(num >>> 8); + out[outOff] = (byte)num; + } + + private byte[] CM5func(byte[] buf, int bufOff, byte[] mac) + { + byte[] sum = new byte[buf.length - bufOff]; + + System.arraycopy(buf, bufOff, sum, 0, mac.length); + + for (int i = 0; i != mac.length; i++) + { + sum[i] = (byte)(sum[i] ^ mac[i]); + } + + return sum; + } + + public void update(byte in) + throws IllegalStateException + { + if (bufOff == buf.length) + { + byte[] sumbuf = new byte[buf.length]; + System.arraycopy(buf, 0, sumbuf, 0, mac.length); + + if (firstStep) + { + firstStep = false; + } + else + { + sumbuf = CM5func(buf, 0, mac); + } + + gost28147MacFunc(workingKey, sumbuf, 0, mac, 0); + bufOff = 0; + } + + buf[bufOff++] = in; + } + + public void update(byte[] in, int inOff, int len) + throws DataLengthException, IllegalStateException + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + byte[] sumbuf = new byte[buf.length]; + System.arraycopy(buf, 0, sumbuf, 0, mac.length); + + if (firstStep) + { + firstStep = false; + } + else + { + sumbuf = CM5func(buf, 0, mac); + } + + gost28147MacFunc(workingKey, sumbuf, 0, mac, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + sumbuf = CM5func(in, inOff, mac); + gost28147MacFunc(workingKey, sumbuf, 0, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int doFinal(byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + //padding with zero + while (bufOff < blockSize) + { + buf[bufOff] = 0; + bufOff++; + } + + byte[] sumbuf = new byte[buf.length]; + System.arraycopy(buf, 0, sumbuf, 0, mac.length); + + if (firstStep) + { + firstStep = false; + } + else + { + sumbuf = CM5func(buf, 0, mac); + } + + gost28147MacFunc(workingKey, sumbuf, 0, mac, 0); + + System.arraycopy(mac, (mac.length/2)-macSize, out, outOff, macSize); + + reset(); + + return macSize; + } + + public void reset() + { + /* + * clean the buffer. + */ + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + + bufOff = 0; + + firstStep = true; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/macs/HMac.java b/src/com/google/bitcoin/bouncycastle/crypto/macs/HMac.java new file mode 100644 index 000000000..5dfd0fa27 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/macs/HMac.java @@ -0,0 +1,199 @@ +package com.google.bitcoin.bouncycastle.crypto.macs; + +import java.util.Hashtable; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.ExtendedDigest; +import com.google.bitcoin.bouncycastle.crypto.Mac; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * HMAC implementation based on RFC2104 + * + * H(K XOR opad, H(K XOR ipad, text)) + */ +public class HMac + implements Mac +{ + private final static byte IPAD = (byte)0x36; + private final static byte OPAD = (byte)0x5C; + + private Digest digest; + private int digestSize; + private int blockLength; + + private byte[] inputPad; + private byte[] outputPad; + + private static Hashtable blockLengths; + + static + { + blockLengths = new Hashtable(); + + blockLengths.put("GOST3411", new Integer(32)); + + blockLengths.put("MD2", new Integer(16)); + blockLengths.put("MD4", new Integer(64)); + blockLengths.put("MD5", new Integer(64)); + + blockLengths.put("RIPEMD128", new Integer(64)); + blockLengths.put("RIPEMD160", new Integer(64)); + + blockLengths.put("SHA-1", new Integer(64)); + blockLengths.put("SHA-224", new Integer(64)); + blockLengths.put("SHA-256", new Integer(64)); + blockLengths.put("SHA-384", new Integer(128)); + blockLengths.put("SHA-512", new Integer(128)); + + blockLengths.put("Tiger", new Integer(64)); + blockLengths.put("Whirlpool", new Integer(64)); + } + + private static int getByteLength( + Digest digest) + { + if (digest instanceof ExtendedDigest) + { + return ((ExtendedDigest)digest).getByteLength(); + } + + Integer b = (Integer)blockLengths.get(digest.getAlgorithmName()); + + if (b == null) + { + throw new IllegalArgumentException("unknown digest passed: " + digest.getAlgorithmName()); + } + + return b.intValue(); + } + + /** + * Base constructor for one of the standard digest algorithms that the + * byteLength of the algorithm is know for. + * + * @param digest the digest. + */ + public HMac( + Digest digest) + { + this(digest, getByteLength(digest)); + } + + private HMac( + Digest digest, + int byteLength) + { + this.digest = digest; + digestSize = digest.getDigestSize(); + + this.blockLength = byteLength; + + inputPad = new byte[blockLength]; + outputPad = new byte[blockLength]; + } + + public String getAlgorithmName() + { + return digest.getAlgorithmName() + "/HMAC"; + } + + public Digest getUnderlyingDigest() + { + return digest; + } + + public void init( + CipherParameters params) + { + digest.reset(); + + byte[] key = ((KeyParameter)params).getKey(); + + if (key.length > blockLength) + { + digest.update(key, 0, key.length); + digest.doFinal(inputPad, 0); + for (int i = digestSize; i < inputPad.length; i++) + { + inputPad[i] = 0; + } + } + else + { + System.arraycopy(key, 0, inputPad, 0, key.length); + for (int i = key.length; i < inputPad.length; i++) + { + inputPad[i] = 0; + } + } + + outputPad = new byte[inputPad.length]; + System.arraycopy(inputPad, 0, outputPad, 0, inputPad.length); + + for (int i = 0; i < inputPad.length; i++) + { + inputPad[i] ^= IPAD; + } + + for (int i = 0; i < outputPad.length; i++) + { + outputPad[i] ^= OPAD; + } + + digest.update(inputPad, 0, inputPad.length); + } + + public int getMacSize() + { + return digestSize; + } + + public void update( + byte in) + { + digest.update(in); + } + + public void update( + byte[] in, + int inOff, + int len) + { + digest.update(in, inOff, len); + } + + public int doFinal( + byte[] out, + int outOff) + { + byte[] tmp = new byte[digestSize]; + digest.doFinal(tmp, 0); + + digest.update(outputPad, 0, outputPad.length); + digest.update(tmp, 0, tmp.length); + + int len = digest.doFinal(out, outOff); + + reset(); + + return len; + } + + /** + * Reset the mac generator. + */ + public void reset() + { + /* + * reset the underlying digest. + */ + digest.reset(); + + /* + * reinitialize the digest. + */ + digest.update(inputPad, 0, inputPad.length); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/macs/ISO9797Alg3Mac.java b/src/com/google/bitcoin/bouncycastle/crypto/macs/ISO9797Alg3Mac.java new file mode 100644 index 000000000..7c5c1013f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/macs/ISO9797Alg3Mac.java @@ -0,0 +1,287 @@ +package com.google.bitcoin.bouncycastle.crypto.macs; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.Mac; +import com.google.bitcoin.bouncycastle.crypto.engines.DESEngine; +import com.google.bitcoin.bouncycastle.crypto.modes.CBCBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.paddings.BlockCipherPadding; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * DES based CBC Block Cipher MAC according to ISO9797, algorithm 3 (ANSI X9.19 Retail MAC) + * + * This could as well be derived from CBCBlockCipherMac, but then the property mac in the base + * class must be changed to protected + */ + +public class ISO9797Alg3Mac + implements Mac +{ + private byte[] mac; + + private byte[] buf; + private int bufOff; + private BlockCipher cipher; + private BlockCipherPadding padding; + + private int macSize; + private KeyParameter lastKey2; + private KeyParameter lastKey3; + + /** + * create a Retail-MAC based on a CBC block cipher. This will produce an + * authentication code of the length of the block size of the cipher. + * + * @param cipher the cipher to be used as the basis of the MAC generation. This must + * be DESEngine. + */ + public ISO9797Alg3Mac( + BlockCipher cipher) + { + this(cipher, cipher.getBlockSize() * 8, null); + } + + /** + * create a Retail-MAC based on a CBC block cipher. This will produce an + * authentication code of the length of the block size of the cipher. + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param padding the padding to be used to complete the last block. + */ + public ISO9797Alg3Mac( + BlockCipher cipher, + BlockCipherPadding padding) + { + this(cipher, cipher.getBlockSize() * 8, padding); + } + + /** + * create a Retail-MAC based on a block cipher with the size of the + * MAC been given in bits. This class uses single DES CBC mode as the basis for the + * MAC generation. + *

    + * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + * or 16 bits if being used as a data authenticator (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + */ + public ISO9797Alg3Mac( + BlockCipher cipher, + int macSizeInBits) + { + this(cipher, macSizeInBits, null); + } + + /** + * create a standard MAC based on a block cipher with the size of the + * MAC been given in bits. This class uses single DES CBC mode as the basis for the + * MAC generation. The final block is decrypted and then encrypted using the + * middle and right part of the key. + *

    + * Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + * or 16 bits if being used as a data authenticator (FIPS Publication 113), + * and in general should be less than the size of the block cipher as it reduces + * the chance of an exhaustive attack (see Handbook of Applied Cryptography). + * + * @param cipher the cipher to be used as the basis of the MAC generation. + * @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + * @param padding the padding to be used to complete the last block. + */ + public ISO9797Alg3Mac( + BlockCipher cipher, + int macSizeInBits, + BlockCipherPadding padding) + { + if ((macSizeInBits % 8) != 0) + { + throw new IllegalArgumentException("MAC size must be multiple of 8"); + } + + if (!(cipher instanceof DESEngine)) + { + throw new IllegalArgumentException("cipher must be instance of DESEngine"); + } + + this.cipher = new CBCBlockCipher(cipher); + this.padding = padding; + this.macSize = macSizeInBits / 8; + + mac = new byte[cipher.getBlockSize()]; + + buf = new byte[cipher.getBlockSize()]; + bufOff = 0; + } + + public String getAlgorithmName() + { + return "ISO9797Alg3"; + } + + public void init(CipherParameters params) + { + reset(); + + if (!(params instanceof KeyParameter)) + { + throw new IllegalArgumentException( + "params must be an instance of KeyParameter"); + } + + // KeyParameter must contain a double or triple length DES key, + // however the underlying cipher is a single DES. The middle and + // right key are used only in the final step. + + KeyParameter kp = (KeyParameter)params; + KeyParameter key1; + byte[] keyvalue = kp.getKey(); + + if (keyvalue.length == 16) + { // Double length DES key + key1 = new KeyParameter(keyvalue, 0, 8); + this.lastKey2 = new KeyParameter(keyvalue, 8, 8); + this.lastKey3 = key1; + } + else if (keyvalue.length == 24) + { // Triple length DES key + key1 = new KeyParameter(keyvalue, 0, 8); + this.lastKey2 = new KeyParameter(keyvalue, 8, 8); + this.lastKey3 = new KeyParameter(keyvalue, 16, 8); + } + else + { + throw new IllegalArgumentException( + "Key must be either 112 or 168 bit long"); + } + + cipher.init(true, key1); + } + + public int getMacSize() + { + return macSize; + } + + public void update( + byte in) + { + if (bufOff == buf.length) + { + cipher.processBlock(buf, 0, mac, 0); + bufOff = 0; + } + + buf[bufOff++] = in; + } + + + public void update( + byte[] in, + int inOff, + int len) + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = cipher.getBlockSize(); + int resultLen = 0; + int gapLen = blockSize - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += cipher.processBlock(buf, 0, mac, 0); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + resultLen += cipher.processBlock(in, inOff, mac, 0); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + public int doFinal( + byte[] out, + int outOff) + { + int blockSize = cipher.getBlockSize(); + + if (padding == null) + { + // + // pad with zeroes + // + while (bufOff < blockSize) + { + buf[bufOff] = 0; + bufOff++; + } + } + else + { + if (bufOff == blockSize) + { + cipher.processBlock(buf, 0, mac, 0); + bufOff = 0; + } + + padding.addPadding(buf, bufOff); + } + + cipher.processBlock(buf, 0, mac, 0); + + // Added to code from base class + DESEngine deseng = new DESEngine(); + + deseng.init(false, this.lastKey2); + deseng.processBlock(mac, 0, mac, 0); + + deseng.init(true, this.lastKey3); + deseng.processBlock(mac, 0, mac, 0); + // **** + + System.arraycopy(mac, 0, out, outOff, macSize); + + reset(); + + return macSize; + } + + + /** + * Reset the mac generator. + */ + public void reset() + { + /* + * clean the buffer. + */ + for (int i = 0; i < buf.length; i++) + { + buf[i] = 0; + } + + bufOff = 0; + + /* + * reset the underlying cipher. + */ + cipher.reset(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/macs/OldHMac.java b/src/com/google/bitcoin/bouncycastle/crypto/macs/OldHMac.java new file mode 100644 index 000000000..8f688e206 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/macs/OldHMac.java @@ -0,0 +1,138 @@ +package com.google.bitcoin.bouncycastle.crypto.macs; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.Mac; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +/** + * HMAC implementation based on RFC2104 + * + * H(K XOR opad, H(K XOR ipad, text)) + */ +public class OldHMac +implements Mac +{ + private final static int BLOCK_LENGTH = 64; + + private final static byte IPAD = (byte)0x36; + private final static byte OPAD = (byte)0x5C; + + private Digest digest; + private int digestSize; + private byte[] inputPad = new byte[BLOCK_LENGTH]; + private byte[] outputPad = new byte[BLOCK_LENGTH]; + + /** + * @deprecated uses incorrect pad for SHA-512 and SHA-384 use HMac. + */ + public OldHMac( + Digest digest) + { + this.digest = digest; + digestSize = digest.getDigestSize(); + } + + public String getAlgorithmName() + { + return digest.getAlgorithmName() + "/HMAC"; + } + + public Digest getUnderlyingDigest() + { + return digest; + } + + public void init( + CipherParameters params) + { + digest.reset(); + + byte[] key = ((KeyParameter)params).getKey(); + + if (key.length > BLOCK_LENGTH) + { + digest.update(key, 0, key.length); + digest.doFinal(inputPad, 0); + for (int i = digestSize; i < inputPad.length; i++) + { + inputPad[i] = 0; + } + } + else + { + System.arraycopy(key, 0, inputPad, 0, key.length); + for (int i = key.length; i < inputPad.length; i++) + { + inputPad[i] = 0; + } + } + + outputPad = new byte[inputPad.length]; + System.arraycopy(inputPad, 0, outputPad, 0, inputPad.length); + + for (int i = 0; i < inputPad.length; i++) + { + inputPad[i] ^= IPAD; + } + + for (int i = 0; i < outputPad.length; i++) + { + outputPad[i] ^= OPAD; + } + + digest.update(inputPad, 0, inputPad.length); + } + + public int getMacSize() + { + return digestSize; + } + + public void update( + byte in) + { + digest.update(in); + } + + public void update( + byte[] in, + int inOff, + int len) + { + digest.update(in, inOff, len); + } + + public int doFinal( + byte[] out, + int outOff) + { + byte[] tmp = new byte[digestSize]; + digest.doFinal(tmp, 0); + + digest.update(outputPad, 0, outputPad.length); + digest.update(tmp, 0, tmp.length); + + int len = digest.doFinal(out, outOff); + + reset(); + + return len; + } + + /** + * Reset the mac generator. + */ + public void reset() + { + /* + * reset the underlying digest. + */ + digest.reset(); + + /* + * reinitialize the digest. + */ + digest.update(inputPad, 0, inputPad.length); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/macs/VMPCMac.java b/src/com/google/bitcoin/bouncycastle/crypto/macs/VMPCMac.java new file mode 100644 index 000000000..780157c36 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/macs/VMPCMac.java @@ -0,0 +1,186 @@ +package com.google.bitcoin.bouncycastle.crypto.macs; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.Mac; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +public class VMPCMac implements Mac +{ + private byte g; + + private byte n = 0; + private byte[] P = null; + private byte s = 0; + + private byte[] T; + private byte[] workingIV; + + private byte[] workingKey; + + private byte x1, x2, x3, x4; + + public int doFinal(byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + // Execute the Post-Processing Phase + for (int r = 1; r < 25; r++) + { + s = P[(s + P[n & 0xff]) & 0xff]; + + x4 = P[(x4 + x3 + r) & 0xff]; + x3 = P[(x3 + x2 + r) & 0xff]; + x2 = P[(x2 + x1 + r) & 0xff]; + x1 = P[(x1 + s + r) & 0xff]; + T[g & 0x1f] = (byte) (T[g & 0x1f] ^ x1); + T[(g + 1) & 0x1f] = (byte) (T[(g + 1) & 0x1f] ^ x2); + T[(g + 2) & 0x1f] = (byte) (T[(g + 2) & 0x1f] ^ x3); + T[(g + 3) & 0x1f] = (byte) (T[(g + 3) & 0x1f] ^ x4); + g = (byte) ((g + 4) & 0x1f); + + byte temp = P[n & 0xff]; + P[n & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + n = (byte) ((n + 1) & 0xff); + } + + // Input T to the IV-phase of the VMPC KSA + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + T[m & 0x1f]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + + // Store 20 new outputs of the VMPC Stream Cipher in table M + byte[] M = new byte[20]; + for (int i = 0; i < 20; i++) + { + s = P[(s + P[i & 0xff]) & 0xff]; + M[i] = P[(P[(P[s & 0xff]) & 0xff] + 1) & 0xff]; + + byte temp = P[i & 0xff]; + P[i & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + + System.arraycopy(M, 0, out, outOff, M.length); + reset(); + + return M.length; + } + + public String getAlgorithmName() + { + return "VMPC-MAC"; + } + + public int getMacSize() + { + return 20; + } + + public void init(CipherParameters params) throws IllegalArgumentException + { + if (!(params instanceof ParametersWithIV)) + { + throw new IllegalArgumentException( + "VMPC-MAC Init parameters must include an IV"); + } + + ParametersWithIV ivParams = (ParametersWithIV) params; + KeyParameter key = (KeyParameter) ivParams.getParameters(); + + if (!(ivParams.getParameters() instanceof KeyParameter)) + { + throw new IllegalArgumentException( + "VMPC-MAC Init parameters must include a key"); + } + + this.workingIV = ivParams.getIV(); + + if (workingIV == null || workingIV.length < 1 || workingIV.length > 768) + { + throw new IllegalArgumentException( + "VMPC-MAC requires 1 to 768 bytes of IV"); + } + + this.workingKey = key.getKey(); + + reset(); + + } + + private void initKey(byte[] keyBytes, byte[] ivBytes) + { + s = 0; + P = new byte[256]; + for (int i = 0; i < 256; i++) + { + P[i] = (byte) i; + } + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + keyBytes[m % keyBytes.length]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + ivBytes[m % ivBytes.length]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + n = 0; + } + + public void reset() + { + initKey(this.workingKey, this.workingIV); + g = x1 = x2 = x3 = x4 = n = 0; + T = new byte[32]; + for (int i = 0; i < 32; i++) + { + T[i] = 0; + } + } + + public void update(byte in) throws IllegalStateException + { + s = P[(s + P[n & 0xff]) & 0xff]; + byte c = (byte) (in ^ P[(P[(P[s & 0xff]) & 0xff] + 1) & 0xff]); + + x4 = P[(x4 + x3) & 0xff]; + x3 = P[(x3 + x2) & 0xff]; + x2 = P[(x2 + x1) & 0xff]; + x1 = P[(x1 + s + c) & 0xff]; + T[g & 0x1f] = (byte) (T[g & 0x1f] ^ x1); + T[(g + 1) & 0x1f] = (byte) (T[(g + 1) & 0x1f] ^ x2); + T[(g + 2) & 0x1f] = (byte) (T[(g + 2) & 0x1f] ^ x3); + T[(g + 3) & 0x1f] = (byte) (T[(g + 3) & 0x1f] ^ x4); + g = (byte) ((g + 4) & 0x1f); + + byte temp = P[n & 0xff]; + P[n & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + n = (byte) ((n + 1) & 0xff); + } + + public void update(byte[] in, int inOff, int len) + throws DataLengthException, IllegalStateException + { + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + for (int i = 0; i < len; i++) + { + update(in[i]); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/AEADBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/AEADBlockCipher.java new file mode 100644 index 000000000..efc4c94ad --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/AEADBlockCipher.java @@ -0,0 +1,108 @@ +package com.google.bitcoin.bouncycastle.crypto.modes; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A block cipher mode that includes authenticated encryption with a streaming mode and optional associated data. + * @see com.google.bitcoin.bouncycastle.crypto.params.AEADParameters + */ +public interface AEADBlockCipher +{ + /** + * initialise the underlying cipher. Parameter can either be an AEADParameters or a ParametersWithIV object. + * + * @param forEncryption true if we are setting up for encryption, false otherwise. + * @param params the necessary parameters for the underlying cipher to be initialised. + * @exception IllegalArgumentException if the params argument is inappropriate. + */ + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException; + + /** + * Return the name of the algorithm. + * + * @return the algorithm name. + */ + public String getAlgorithmName(); + + /** + * return the cipher this object wraps. + * + * @return the cipher this object wraps. + */ + public BlockCipher getUnderlyingCipher(); + + /** + * encrypt/decrypt a single byte. + * + * @param in the byte to be processed. + * @param out the output buffer the processed byte goes into. + * @param outOff the offset into the output byte array the processed data starts at. + * @return the number of bytes written to out. + * @exception DataLengthException if the output buffer is too small. + */ + public int processByte(byte in, byte[] out, int outOff) + throws DataLengthException; + + /** + * process a block of bytes from in putting the result into out. + * + * @param in the input byte array. + * @param inOff the offset into the in array where the data to be processed starts. + * @param len the number of bytes to be processed. + * @param out the output buffer the processed bytes go into. + * @param outOff the offset into the output byte array the processed data starts at. + * @return the number of bytes written to out. + * @exception DataLengthException if the output buffer is too small. + */ + public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) + throws DataLengthException; + + /** + * Finish the operation either appending or verifying the MAC at the end of the data. + * + * @param out space for any resulting output data. + * @param outOff offset into out to start copying the data at. + * @return number of bytes written into out. + * @throws IllegalStateException if the cipher is in an inappropriate state. + * @throws com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException if the MAC fails to match. + */ + public int doFinal(byte[] out, int outOff) + throws IllegalStateException, InvalidCipherTextException; + + /** + * Return the value of the MAC associated with the last stream processed. + * + * @return MAC for plaintext data. + */ + public byte[] getMac(); + + /** + * return the size of the output buffer required for a processBytes + * an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to processBytes + * with len bytes of input. + */ + public int getUpdateOutputSize(int len); + + /** + * return the size of the output buffer required for a processBytes plus a + * doFinal with an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to processBytes and doFinal + * with len bytes of input. + */ + public int getOutputSize(int len); + + /** + * Reset the cipher. After resetting the cipher is in the same state + * as it was after the last init (if there was one). + */ + public void reset(); +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/CBCBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/CBCBlockCipher.java new file mode 100644 index 000000000..c485df838 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/CBCBlockCipher.java @@ -0,0 +1,235 @@ +package com.google.bitcoin.bouncycastle.crypto.modes; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; +import com.google.bitcoin.bouncycastle.util.Arrays; + +/** + * implements Cipher-Block-Chaining (CBC) mode on top of a simple cipher. + */ +public class CBCBlockCipher + implements BlockCipher +{ + private byte[] IV; + private byte[] cbcV; + private byte[] cbcNextV; + + private int blockSize; + private BlockCipher cipher = null; + private boolean encrypting; + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of chaining. + */ + public CBCBlockCipher( + BlockCipher cipher) + { + this.cipher = cipher; + this.blockSize = cipher.getBlockSize(); + + this.IV = new byte[blockSize]; + this.cbcV = new byte[blockSize]; + this.cbcNextV = new byte[blockSize]; + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * Initialise the cipher and, possibly, the initialisation vector (IV). + * If an IV isn't passed as part of the parameter, the IV will be all zeros. + * + * @param encrypting if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + throws IllegalArgumentException + { + this.encrypting = encrypting; + + if (params instanceof ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)params; + byte[] iv = ivParam.getIV(); + + if (iv.length != blockSize) + { + throw new IllegalArgumentException("initialisation vector must be the same length as block size"); + } + + System.arraycopy(iv, 0, IV, 0, iv.length); + + reset(); + + cipher.init(encrypting, ivParam.getParameters()); + } + else + { + reset(); + + cipher.init(encrypting, params); + } + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/CBC". + */ + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/CBC"; + } + + /** + * return the block size of the underlying cipher. + * + * @return the block size of the underlying cipher. + */ + public int getBlockSize() + { + return cipher.getBlockSize(); + } + + /** + * Process one block of input from the array in and write it to + * the out array. + * + * @param in the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param out the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + return (encrypting) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff); + } + + /** + * reset the chaining vector back to the IV and reset the underlying + * cipher. + */ + public void reset() + { + System.arraycopy(IV, 0, cbcV, 0, IV.length); + Arrays.fill(cbcNextV, (byte)0); + + cipher.reset(); + } + + /** + * Do the appropriate chaining step for CBC mode encryption. + * + * @param in the array containing the data to be encrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + /* + * XOR the cbcV and the input, + * then encrypt the cbcV + */ + for (int i = 0; i < blockSize; i++) + { + cbcV[i] ^= in[inOff + i]; + } + + int length = cipher.processBlock(cbcV, 0, out, outOff); + + /* + * copy ciphertext to cbcV + */ + System.arraycopy(out, outOff, cbcV, 0, cbcV.length); + + return length; + } + + /** + * Do the appropriate chaining step for CBC mode decryption. + * + * @param in the array containing the data to be decrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the decrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + System.arraycopy(in, inOff, cbcNextV, 0, blockSize); + + int length = cipher.processBlock(in, inOff, out, outOff); + + /* + * XOR the cbcV and the output + */ + for (int i = 0; i < blockSize; i++) + { + out[outOff + i] ^= cbcV[i]; + } + + /* + * swap the back up buffer into next position + */ + byte[] tmp; + + tmp = cbcV; + cbcV = cbcNextV; + cbcNextV = tmp; + + return length; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/CCMBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/CCMBlockCipher.java new file mode 100644 index 000000000..55a58745c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/CCMBlockCipher.java @@ -0,0 +1,338 @@ +package com.google.bitcoin.bouncycastle.crypto.modes; + +import java.io.ByteArrayOutputStream; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; +import com.google.bitcoin.bouncycastle.crypto.Mac; +import com.google.bitcoin.bouncycastle.crypto.macs.CBCBlockCipherMac; +import com.google.bitcoin.bouncycastle.crypto.params.AEADParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; +import com.google.bitcoin.bouncycastle.util.Arrays; + +/** + * Implements the Counter with Cipher Block Chaining mode (CCM) detailed in + * NIST Special Publication 800-38C. + *

    + * Note: this mode is a packet mode - it needs all the data up front. + */ +public class CCMBlockCipher + implements AEADBlockCipher +{ + private BlockCipher cipher; + private int blockSize; + private boolean forEncryption; + private byte[] nonce; + private byte[] associatedText; + private int macSize; + private CipherParameters keyParam; + private byte[] macBlock; + private ByteArrayOutputStream data = new ByteArrayOutputStream(); + + /** + * Basic constructor. + * + * @param c the block cipher to be used. + */ + public CCMBlockCipher(BlockCipher c) + { + this.cipher = c; + this.blockSize = c.getBlockSize(); + this.macBlock = new byte[blockSize]; + + if (blockSize != 16) + { + throw new IllegalArgumentException("cipher required with a block size of 16."); + } + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + this.forEncryption = forEncryption; + + if (params instanceof AEADParameters) + { + AEADParameters param = (AEADParameters)params; + + nonce = param.getNonce(); + associatedText = param.getAssociatedText(); + macSize = param.getMacSize() / 8; + keyParam = param.getKey(); + } + else if (params instanceof ParametersWithIV) + { + ParametersWithIV param = (ParametersWithIV)params; + + nonce = param.getIV(); + associatedText = null; + macSize = macBlock.length / 2; + keyParam = param.getParameters(); + } + else + { + throw new IllegalArgumentException("invalid parameters passed to CCM"); + } + } + + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/CCM"; + } + + public int processByte(byte in, byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + data.write(in); + + return 0; + } + + public int processBytes(byte[] in, int inOff, int inLen, byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + data.write(in, inOff, inLen); + + return 0; + } + + public int doFinal(byte[] out, int outOff) + throws IllegalStateException, InvalidCipherTextException + { + byte[] text = data.toByteArray(); + byte[] enc = processPacket(text, 0, text.length); + + System.arraycopy(enc, 0, out, outOff, enc.length); + + reset(); + + return enc.length; + } + + public void reset() + { + cipher.reset(); + data.reset(); + } + + /** + * Returns a byte array containing the mac calculated as part of the + * last encrypt or decrypt operation. + * + * @return the last mac calculated. + */ + public byte[] getMac() + { + byte[] mac = new byte[macSize]; + + System.arraycopy(macBlock, 0, mac, 0, mac.length); + + return mac; + } + + public int getUpdateOutputSize(int len) + { + return 0; + } + + public int getOutputSize(int len) + { + if (forEncryption) + { + return data.size() + len + macSize; + } + else + { + return data.size() + len - macSize; + } + } + + public byte[] processPacket(byte[] in, int inOff, int inLen) + throws IllegalStateException, InvalidCipherTextException + { + if (keyParam == null) + { + throw new IllegalStateException("CCM cipher unitialized."); + } + + BlockCipher ctrCipher = new SICBlockCipher(cipher); + byte[] iv = new byte[blockSize]; + byte[] out; + + iv[0] = (byte)(((15 - nonce.length) - 1) & 0x7); + + System.arraycopy(nonce, 0, iv, 1, nonce.length); + + ctrCipher.init(forEncryption, new ParametersWithIV(keyParam, iv)); + + if (forEncryption) + { + int index = inOff; + int outOff = 0; + + out = new byte[inLen + macSize]; + + calculateMac(in, inOff, inLen, macBlock); + + ctrCipher.processBlock(macBlock, 0, macBlock, 0); // S0 + + while (index < inLen - blockSize) // S1... + { + ctrCipher.processBlock(in, index, out, outOff); + outOff += blockSize; + index += blockSize; + } + + byte[] block = new byte[blockSize]; + + System.arraycopy(in, index, block, 0, inLen - index); + + ctrCipher.processBlock(block, 0, block, 0); + + System.arraycopy(block, 0, out, outOff, inLen - index); + + outOff += inLen - index; + + System.arraycopy(macBlock, 0, out, outOff, out.length - outOff); + } + else + { + int index = inOff; + int outOff = 0; + + out = new byte[inLen - macSize]; + + System.arraycopy(in, inOff + inLen - macSize, macBlock, 0, macSize); + + ctrCipher.processBlock(macBlock, 0, macBlock, 0); + + for (int i = macSize; i != macBlock.length; i++) + { + macBlock[i] = 0; + } + + while (outOff < out.length - blockSize) + { + ctrCipher.processBlock(in, index, out, outOff); + outOff += blockSize; + index += blockSize; + } + + byte[] block = new byte[blockSize]; + + System.arraycopy(in, index, block, 0, out.length - outOff); + + ctrCipher.processBlock(block, 0, block, 0); + + System.arraycopy(block, 0, out, outOff, out.length - outOff); + + byte[] calculatedMacBlock = new byte[blockSize]; + + calculateMac(out, 0, out.length, calculatedMacBlock); + + if (!Arrays.constantTimeAreEqual(macBlock, calculatedMacBlock)) + { + throw new InvalidCipherTextException("mac check in CCM failed"); + } + } + + return out; + } + + private int calculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock) + { + Mac cMac = new CBCBlockCipherMac(cipher, macSize * 8); + + cMac.init(keyParam); + + // + // build b0 + // + byte[] b0 = new byte[16]; + + if (hasAssociatedText()) + { + b0[0] |= 0x40; + } + + b0[0] |= (((cMac.getMacSize() - 2) / 2) & 0x7) << 3; + + b0[0] |= ((15 - nonce.length) - 1) & 0x7; + + System.arraycopy(nonce, 0, b0, 1, nonce.length); + + int q = dataLen; + int count = 1; + while (q > 0) + { + b0[b0.length - count] = (byte)(q & 0xff); + q >>>= 8; + count++; + } + + cMac.update(b0, 0, b0.length); + + // + // process associated text + // + if (hasAssociatedText()) + { + int extra; + + if (associatedText.length < ((1 << 16) - (1 << 8))) + { + cMac.update((byte)(associatedText.length >> 8)); + cMac.update((byte)associatedText.length); + + extra = 2; + } + else // can't go any higher than 2^32 + { + cMac.update((byte)0xff); + cMac.update((byte)0xfe); + cMac.update((byte)(associatedText.length >> 24)); + cMac.update((byte)(associatedText.length >> 16)); + cMac.update((byte)(associatedText.length >> 8)); + cMac.update((byte)associatedText.length); + + extra = 6; + } + + cMac.update(associatedText, 0, associatedText.length); + + extra = (extra + associatedText.length) % 16; + if (extra != 0) + { + for (int i = 0; i != 16 - extra; i++) + { + cMac.update((byte)0x00); + } + } + } + + // + // add the text + // + cMac.update(data, dataOff, dataLen); + + return cMac.doFinal(macBlock, 0); + } + + private boolean hasAssociatedText() + { + return associatedText != null && associatedText.length != 0; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/CFBBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/CFBBlockCipher.java new file mode 100644 index 000000000..d277fd5ad --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/CFBBlockCipher.java @@ -0,0 +1,250 @@ +package com.google.bitcoin.bouncycastle.crypto.modes; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +/** + * implements a Cipher-FeedBack (CFB) mode on top of a simple cipher. + */ +public class CFBBlockCipher + implements BlockCipher +{ + private byte[] IV; + private byte[] cfbV; + private byte[] cfbOutV; + + private int blockSize; + private BlockCipher cipher = null; + private boolean encrypting; + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of the + * feedback mode. + * @param bitBlockSize the block size in bits (note: a multiple of 8) + */ + public CFBBlockCipher( + BlockCipher cipher, + int bitBlockSize) + { + this.cipher = cipher; + this.blockSize = bitBlockSize / 8; + + this.IV = new byte[cipher.getBlockSize()]; + this.cfbV = new byte[cipher.getBlockSize()]; + this.cfbOutV = new byte[cipher.getBlockSize()]; + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * Initialise the cipher and, possibly, the initialisation vector (IV). + * If an IV isn't passed as part of the parameter, the IV will be all zeros. + * An IV which is too short is handled in FIPS compliant fashion. + * + * @param encrypting if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, + CipherParameters params) + throws IllegalArgumentException + { + this.encrypting = encrypting; + + if (params instanceof ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)params; + byte[] iv = ivParam.getIV(); + + if (iv.length < IV.length) + { + // prepend the supplied IV with zeros (per FIPS PUB 81) + System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length); + for (int i = 0; i < IV.length - iv.length; i++) + { + IV[i] = 0; + } + } + else + { + System.arraycopy(iv, 0, IV, 0, IV.length); + } + + reset(); + + cipher.init(true, ivParam.getParameters()); + } + else + { + reset(); + + cipher.init(true, params); + } + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/CFB" + * and the block size in bits. + */ + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/CFB" + (blockSize * 8); + } + + /** + * return the block size we are operating at. + * + * @return the block size we are operating at (in bytes). + */ + public int getBlockSize() + { + return blockSize; + } + + /** + * Process one block of input from the array in and write it to + * the out array. + * + * @param in the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param out the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + return (encrypting) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff); + } + + /** + * Do the appropriate processing for CFB mode encryption. + * + * @param in the array containing the data to be encrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + cipher.processBlock(cfbV, 0, cfbOutV, 0); + + // + // XOR the cfbV with the plaintext producing the ciphertext + // + for (int i = 0; i < blockSize; i++) + { + out[outOff + i] = (byte)(cfbOutV[i] ^ in[inOff + i]); + } + + // + // change over the input block. + // + System.arraycopy(cfbV, blockSize, cfbV, 0, cfbV.length - blockSize); + System.arraycopy(out, outOff, cfbV, cfbV.length - blockSize, blockSize); + + return blockSize; + } + + /** + * Do the appropriate processing for CFB mode decryption. + * + * @param in the array containing the data to be decrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + cipher.processBlock(cfbV, 0, cfbOutV, 0); + + // + // change over the input block. + // + System.arraycopy(cfbV, blockSize, cfbV, 0, cfbV.length - blockSize); + System.arraycopy(in, inOff, cfbV, cfbV.length - blockSize, blockSize); + + // + // XOR the cfbV with the ciphertext producing the plaintext + // + for (int i = 0; i < blockSize; i++) + { + out[outOff + i] = (byte)(cfbOutV[i] ^ in[inOff + i]); + } + + return blockSize; + } + + /** + * reset the chaining vector back to the IV and reset the underlying + * cipher. + */ + public void reset() + { + System.arraycopy(IV, 0, cfbV, 0, IV.length); + + cipher.reset(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/CTSBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/CTSBlockCipher.java new file mode 100644 index 000000000..36e8ec99c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/CTSBlockCipher.java @@ -0,0 +1,265 @@ +package com.google.bitcoin.bouncycastle.crypto.modes; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.BufferedBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A Cipher Text Stealing (CTS) mode cipher. CTS allows block ciphers to + * be used to produce cipher text which is the same length as the plain text. + */ +public class CTSBlockCipher + extends BufferedBlockCipher +{ + private int blockSize; + + /** + * Create a buffered block cipher that uses Cipher Text Stealing + * + * @param cipher the underlying block cipher this buffering object wraps. + */ + public CTSBlockCipher( + BlockCipher cipher) + { + if ((cipher instanceof OFBBlockCipher) || (cipher instanceof CFBBlockCipher)) + { + throw new IllegalArgumentException("CTSBlockCipher can only accept ECB, or CBC ciphers"); + } + + this.cipher = cipher; + + blockSize = cipher.getBlockSize(); + + buf = new byte[blockSize * 2]; + bufOff = 0; + } + + /** + * return the size of the output buffer required for an update + * an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update + * with len bytes of input. + */ + public int getUpdateOutputSize( + int len) + { + int total = len + bufOff; + int leftOver = total % buf.length; + + if (leftOver == 0) + { + return total - buf.length; + } + + return total - leftOver; + } + + /** + * return the size of the output buffer required for an update plus a + * doFinal with an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update and doFinal + * with len bytes of input. + */ + public int getOutputSize( + int len) + { + return len + bufOff; + } + + /** + * process a single byte, producing an output block if neccessary. + * + * @param in the input byte. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processByte( + byte in, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + int resultLen = 0; + + if (bufOff == buf.length) + { + resultLen = cipher.processBlock(buf, 0, out, outOff); + System.arraycopy(buf, blockSize, buf, 0, blockSize); + + bufOff = blockSize; + } + + buf[bufOff++] = in; + + return resultLen; + } + + /** + * process an array of bytes, producing output if necessary. + * + * @param in the input byte array. + * @param inOff the offset at which the input data starts. + * @param len the number of bytes to be copied out of the input array. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = getBlockSize(); + int length = getUpdateOutputSize(len); + + if (length > 0) + { + if ((outOff + length) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + } + + int resultLen = 0; + int gapLen = buf.length - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += cipher.processBlock(buf, 0, out, outOff); + System.arraycopy(buf, blockSize, buf, 0, blockSize); + + bufOff = blockSize; + + len -= gapLen; + inOff += gapLen; + + while (len > blockSize) + { + System.arraycopy(in, inOff, buf, bufOff, blockSize); + resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen); + System.arraycopy(buf, blockSize, buf, 0, blockSize); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + + return resultLen; + } + + /** + * Process the last block in the buffer. + * + * @param out the array the block currently being held is copied into. + * @param outOff the offset at which the copying starts. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there is insufficient space in out for + * the output. + * @exception IllegalStateException if the underlying cipher is not + * initialised. + * @exception InvalidCipherTextException if cipher text decrypts wrongly (in + * case the exception will never get thrown). + */ + public int doFinal( + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException, InvalidCipherTextException + { + if (bufOff + outOff > out.length) + { + throw new DataLengthException("output buffer to small in doFinal"); + } + + int blockSize = cipher.getBlockSize(); + int len = bufOff - blockSize; + byte[] block = new byte[blockSize]; + + if (forEncryption) + { + cipher.processBlock(buf, 0, block, 0); + + if (bufOff < blockSize) + { + throw new DataLengthException("need at least one block of input for CTS"); + } + + for (int i = bufOff; i != buf.length; i++) + { + buf[i] = block[i - blockSize]; + } + + for (int i = blockSize; i != bufOff; i++) + { + buf[i] ^= block[i - blockSize]; + } + + if (cipher instanceof CBCBlockCipher) + { + BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher(); + + c.processBlock(buf, blockSize, out, outOff); + } + else + { + cipher.processBlock(buf, blockSize, out, outOff); + } + + System.arraycopy(block, 0, out, outOff + blockSize, len); + } + else + { + byte[] lastBlock = new byte[blockSize]; + + if (cipher instanceof CBCBlockCipher) + { + BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher(); + + c.processBlock(buf, 0, block, 0); + } + else + { + cipher.processBlock(buf, 0, block, 0); + } + + for (int i = blockSize; i != bufOff; i++) + { + lastBlock[i - blockSize] = (byte)(block[i - blockSize] ^ buf[i]); + } + + System.arraycopy(buf, blockSize, block, 0, len); + + cipher.processBlock(block, 0, out, outOff); + System.arraycopy(lastBlock, 0, out, outOff + blockSize, len); + } + + int offset = bufOff; + + reset(); + + return offset; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/EAXBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/EAXBlockCipher.java new file mode 100644 index 000000000..5457faf91 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/EAXBlockCipher.java @@ -0,0 +1,304 @@ +package com.google.bitcoin.bouncycastle.crypto.modes; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; +import com.google.bitcoin.bouncycastle.crypto.Mac; +import com.google.bitcoin.bouncycastle.crypto.macs.CMac; +import com.google.bitcoin.bouncycastle.crypto.params.AEADParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; +import com.google.bitcoin.bouncycastle.util.Arrays; + +/** + * A Two-Pass Authenticated-Encryption Scheme Optimized for Simplicity and + * Efficiency - by M. Bellare, P. Rogaway, D. Wagner. + * + * http://www.cs.ucdavis.edu/~rogaway/papers/eax.pdf + * + * EAX is an AEAD scheme based on CTR and OMAC1/CMAC, that uses a single block + * cipher to encrypt and authenticate data. It's on-line (the length of a + * message isn't needed to begin processing it), has good performances, it's + * simple and provably secure (provided the underlying block cipher is secure). + * + * Of course, this implementations is NOT thread-safe. + */ +public class EAXBlockCipher + implements AEADBlockCipher +{ + private static final byte nTAG = 0x0; + + private static final byte hTAG = 0x1; + + private static final byte cTAG = 0x2; + + private SICBlockCipher cipher; + + private boolean forEncryption; + + private int blockSize; + + private Mac mac; + + private byte[] nonceMac; + private byte[] associatedTextMac; + private byte[] macBlock; + + private int macSize; + private byte[] bufBlock; + private int bufOff; + + /** + * Constructor that accepts an instance of a block cipher engine. + * + * @param cipher the engine to use + */ + public EAXBlockCipher(BlockCipher cipher) + { + blockSize = cipher.getBlockSize(); + mac = new CMac(cipher); + macBlock = new byte[blockSize]; + bufBlock = new byte[blockSize * 2]; + associatedTextMac = new byte[mac.getMacSize()]; + nonceMac = new byte[mac.getMacSize()]; + this.cipher = new SICBlockCipher(cipher); + } + + public String getAlgorithmName() + { + return cipher.getUnderlyingCipher().getAlgorithmName() + "/EAX"; + } + + public BlockCipher getUnderlyingCipher() + { + return cipher.getUnderlyingCipher(); + } + + public int getBlockSize() + { + return cipher.getBlockSize(); + } + + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + this.forEncryption = forEncryption; + + byte[] nonce, associatedText; + CipherParameters keyParam; + + if (params instanceof AEADParameters) + { + AEADParameters param = (AEADParameters)params; + + nonce = param.getNonce(); + associatedText = param.getAssociatedText(); + macSize = param.getMacSize() / 8; + keyParam = param.getKey(); + } + else if (params instanceof ParametersWithIV) + { + ParametersWithIV param = (ParametersWithIV)params; + + nonce = param.getIV(); + associatedText = new byte[0]; + macSize = mac.getMacSize() / 2; + keyParam = param.getParameters(); + } + else + { + throw new IllegalArgumentException("invalid parameters passed to EAX"); + } + + byte[] tag = new byte[blockSize]; + + mac.init(keyParam); + tag[blockSize - 1] = hTAG; + mac.update(tag, 0, blockSize); + mac.update(associatedText, 0, associatedText.length); + mac.doFinal(associatedTextMac, 0); + + tag[blockSize - 1] = nTAG; + mac.update(tag, 0, blockSize); + mac.update(nonce, 0, nonce.length); + mac.doFinal(nonceMac, 0); + + tag[blockSize - 1] = cTAG; + mac.update(tag, 0, blockSize); + + cipher.init(true, new ParametersWithIV(keyParam, nonceMac)); + } + + private void calculateMac() + { + byte[] outC = new byte[blockSize]; + mac.doFinal(outC, 0); + + for (int i = 0; i < macBlock.length; i++) + { + macBlock[i] = (byte)(nonceMac[i] ^ associatedTextMac[i] ^ outC[i]); + } + } + + public void reset() + { + reset(true); + } + + private void reset( + boolean clearMac) + { + cipher.reset(); + mac.reset(); + + bufOff = 0; + Arrays.fill(bufBlock, (byte)0); + + if (clearMac) + { + Arrays.fill(macBlock, (byte)0); + } + + byte[] tag = new byte[blockSize]; + tag[blockSize - 1] = cTAG; + mac.update(tag, 0, blockSize); + } + + public int processByte(byte in, byte[] out, int outOff) + throws DataLengthException + { + return process(in, out, outOff); + } + + public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) + throws DataLengthException + { + int resultLen = 0; + + for (int i = 0; i != len; i++) + { + resultLen += process(in[inOff + i], out, outOff + resultLen); + } + + return resultLen; + } + + public int doFinal(byte[] out, int outOff) + throws IllegalStateException, InvalidCipherTextException + { + int extra = bufOff; + byte[] tmp = new byte[bufBlock.length]; + + bufOff = 0; + + if (forEncryption) + { + cipher.processBlock(bufBlock, 0, tmp, 0); + cipher.processBlock(bufBlock, blockSize, tmp, blockSize); + + System.arraycopy(tmp, 0, out, outOff, extra); + + mac.update(tmp, 0, extra); + + calculateMac(); + + System.arraycopy(macBlock, 0, out, outOff + extra, macSize); + + reset(false); + + return extra + macSize; + } + else + { + if (extra > macSize) + { + mac.update(bufBlock, 0, extra - macSize); + + cipher.processBlock(bufBlock, 0, tmp, 0); + cipher.processBlock(bufBlock, blockSize, tmp, blockSize); + + System.arraycopy(tmp, 0, out, outOff, extra - macSize); + } + + calculateMac(); + + if (!verifyMac(bufBlock, extra - macSize)) + { + throw new InvalidCipherTextException("mac check in EAX failed"); + } + + reset(false); + + return extra - macSize; + } + } + + public byte[] getMac() + { + byte[] mac = new byte[macSize]; + + System.arraycopy(macBlock, 0, mac, 0, macSize); + + return mac; + } + + public int getUpdateOutputSize(int len) + { + return ((len + bufOff) / blockSize) * blockSize; + } + + public int getOutputSize(int len) + { + if (forEncryption) + { + return len + bufOff + macSize; + } + else + { + return len + bufOff - macSize; + } + } + + private int process(byte b, byte[] out, int outOff) + { + bufBlock[bufOff++] = b; + + if (bufOff == bufBlock.length) + { + int size; + + if (forEncryption) + { + size = cipher.processBlock(bufBlock, 0, out, outOff); + + mac.update(out, outOff, blockSize); + } + else + { + mac.update(bufBlock, 0, blockSize); + + size = cipher.processBlock(bufBlock, 0, out, outOff); + } + + bufOff = blockSize; + System.arraycopy(bufBlock, blockSize, bufBlock, 0, blockSize); + + return size; + } + + return 0; + } + + private boolean verifyMac(byte[] mac, int off) + { + for (int i = 0; i < macSize; i++) + { + if (macBlock[i] != mac[off + i]) + { + return false; + } + } + + return true; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/GCMBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/GCMBlockCipher.java new file mode 100644 index 000000000..ac783af97 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/GCMBlockCipher.java @@ -0,0 +1,416 @@ +package com.google.bitcoin.bouncycastle.crypto.modes; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; +import com.google.bitcoin.bouncycastle.crypto.modes.gcm.GCMMultiplier; +import com.google.bitcoin.bouncycastle.crypto.modes.gcm.Tables8kGCMMultiplier; +import com.google.bitcoin.bouncycastle.crypto.params.AEADParameters; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; +import com.google.bitcoin.bouncycastle.crypto.util.Pack; +import com.google.bitcoin.bouncycastle.util.Arrays; + +/** + * Implements the Galois/Counter mode (GCM) detailed in + * NIST Special Publication 800-38D. + */ +public class GCMBlockCipher + implements AEADBlockCipher +{ + private static final int BLOCK_SIZE = 16; + private static final byte[] ZEROES = new byte[BLOCK_SIZE]; + + // not final due to a compiler bug + private BlockCipher cipher; + private GCMMultiplier multiplier; + + // These fields are set by init and not modified by processing + private boolean forEncryption; + private int macSize; + private byte[] nonce; + private byte[] A; + private KeyParameter keyParam; + private byte[] H; + private byte[] initS; + private byte[] J0; + + // These fields are modified during processing + private byte[] bufBlock; + private byte[] macBlock; + private byte[] S; + private byte[] counter; + private int bufOff; + private long totalLength; + + public GCMBlockCipher(BlockCipher c) + { + this(c, null); + } + + public GCMBlockCipher(BlockCipher c, GCMMultiplier m) + { + if (c.getBlockSize() != BLOCK_SIZE) + { + throw new IllegalArgumentException( + "cipher required with a block size of " + BLOCK_SIZE + "."); + } + + if (m == null) + { + // TODO Consider a static property specifying default multiplier + m = new Tables8kGCMMultiplier(); + } + + this.cipher = c; + this.multiplier = m; + } + + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/GCM"; + } + + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException + { + this.forEncryption = forEncryption; + this.macBlock = null; + + if (params instanceof AEADParameters) + { + AEADParameters param = (AEADParameters)params; + + nonce = param.getNonce(); + A = param.getAssociatedText(); + + int macSizeBits = param.getMacSize(); + if (macSizeBits < 96 || macSizeBits > 128 || macSizeBits % 8 != 0) + { + throw new IllegalArgumentException("Invalid value for MAC size: " + macSizeBits); + } + + macSize = macSizeBits / 8; + keyParam = param.getKey(); + } + else if (params instanceof ParametersWithIV) + { + ParametersWithIV param = (ParametersWithIV)params; + + nonce = param.getIV(); + A = null; + macSize = 16; + keyParam = (KeyParameter)param.getParameters(); + } + else + { + throw new IllegalArgumentException("invalid parameters passed to GCM"); + } + + int bufLength = forEncryption ? BLOCK_SIZE : (BLOCK_SIZE + macSize); + this.bufBlock = new byte[bufLength]; + + if (nonce == null || nonce.length < 1) + { + throw new IllegalArgumentException("IV must be at least 1 byte"); + } + + if (A == null) + { + // Avoid lots of null checks + A = new byte[0]; + } + + // Cipher always used in forward mode + cipher.init(true, keyParam); + + // TODO This should be configurable by init parameters + // (but must be 16 if nonce length not 12) (BLOCK_SIZE?) +// this.tagLength = 16; + + this.H = new byte[BLOCK_SIZE]; + cipher.processBlock(ZEROES, 0, H, 0); + multiplier.init(H); + + this.initS = gHASH(A); + + if (nonce.length == 12) + { + this.J0 = new byte[16]; + System.arraycopy(nonce, 0, J0, 0, nonce.length); + this.J0[15] = 0x01; + } + else + { + this.J0 = gHASH(nonce); + byte[] X = new byte[16]; + packLength((long)nonce.length * 8, X, 8); + xor(this.J0, X); + multiplier.multiplyH(this.J0); + } + + this.S = Arrays.clone(initS); + this.counter = Arrays.clone(J0); + this.bufOff = 0; + this.totalLength = 0; + } + + public byte[] getMac() + { + return Arrays.clone(macBlock); + } + + public int getOutputSize(int len) + { + if (forEncryption) + { + return len + bufOff + macSize; + } + + return len + bufOff - macSize; + } + + public int getUpdateOutputSize(int len) + { + return ((len + bufOff) / BLOCK_SIZE) * BLOCK_SIZE; + } + + public int processByte(byte in, byte[] out, int outOff) + throws DataLengthException + { + return process(in, out, outOff); + } + + public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) + throws DataLengthException + { + int resultLen = 0; + + for (int i = 0; i != len; i++) + { +// resultLen += process(in[inOff + i], out, outOff + resultLen); + bufBlock[bufOff++] = in[inOff + i]; + + if (bufOff == bufBlock.length) + { + gCTRBlock(bufBlock, BLOCK_SIZE, out, outOff + resultLen); + if (!forEncryption) + { + System.arraycopy(bufBlock, BLOCK_SIZE, bufBlock, 0, macSize); + } +// bufOff = 0; + bufOff = bufBlock.length - BLOCK_SIZE; +// return bufBlock.Length; + resultLen += BLOCK_SIZE; + } + } + + return resultLen; + } + + private int process(byte in, byte[] out, int outOff) + throws DataLengthException + { + bufBlock[bufOff++] = in; + + if (bufOff == bufBlock.length) + { + gCTRBlock(bufBlock, BLOCK_SIZE, out, outOff); + if (!forEncryption) + { + System.arraycopy(bufBlock, BLOCK_SIZE, bufBlock, 0, macSize); + } +// bufOff = 0; + bufOff = bufBlock.length - BLOCK_SIZE; +// return bufBlock.length; + return BLOCK_SIZE; + } + + return 0; + } + + public int doFinal(byte[] out, int outOff) + throws IllegalStateException, InvalidCipherTextException + { + int extra = bufOff; + if (!forEncryption) + { + if (extra < macSize) + { + throw new InvalidCipherTextException("data too short"); + } + extra -= macSize; + } + + if (extra > 0) + { + byte[] tmp = new byte[BLOCK_SIZE]; + System.arraycopy(bufBlock, 0, tmp, 0, extra); + gCTRBlock(tmp, extra, out, outOff); + } + + // Final gHASH + byte[] X = new byte[16]; + packLength((long)A.length * 8, X, 0); + packLength(totalLength * 8, X, 8); + + xor(S, X); + multiplier.multiplyH(S); + + // TODO Fix this if tagLength becomes configurable + // T = MSBt(GCTRk(J0,S)) + byte[] tag = new byte[BLOCK_SIZE]; + cipher.processBlock(J0, 0, tag, 0); + xor(tag, S); + + int resultLen = extra; + + // We place into macBlock our calculated value for T + this.macBlock = new byte[macSize]; + System.arraycopy(tag, 0, macBlock, 0, macSize); + + if (forEncryption) + { + // Append T to the message + System.arraycopy(macBlock, 0, out, outOff + bufOff, macSize); + resultLen += macSize; + } + else + { + // Retrieve the T value from the message and compare to calculated one + byte[] msgMac = new byte[macSize]; + System.arraycopy(bufBlock, extra, msgMac, 0, macSize); + if (!Arrays.constantTimeAreEqual(this.macBlock, msgMac)) + { + throw new InvalidCipherTextException("mac check in GCM failed"); + } + } + + reset(false); + + return resultLen; + } + + public void reset() + { + reset(true); + } + + private void reset( + boolean clearMac) + { + S = Arrays.clone(initS); + counter = Arrays.clone(J0); + bufOff = 0; + totalLength = 0; + + if (bufBlock != null) + { + Arrays.fill(bufBlock, (byte)0); + } + + if (clearMac) + { + macBlock = null; + } + + cipher.reset(); + } + + private void gCTRBlock(byte[] buf, int bufCount, byte[] out, int outOff) + { +// inc(counter); + for (int i = 15; i >= 12; --i) + { + byte b = (byte)((counter[i] + 1) & 0xff); + counter[i] = b; + + if (b != 0) + { + break; + } + } + + byte[] tmp = new byte[BLOCK_SIZE]; + cipher.processBlock(counter, 0, tmp, 0); + + byte[] hashBytes; + if (forEncryption) + { + System.arraycopy(ZEROES, bufCount, tmp, bufCount, BLOCK_SIZE - bufCount); + hashBytes = tmp; + } + else + { + hashBytes = buf; + } + + for (int i = bufCount - 1; i >= 0; --i) + { + tmp[i] ^= buf[i]; + out[outOff + i] = tmp[i]; + } + +// gHASHBlock(hashBytes); + xor(S, hashBytes); + multiplier.multiplyH(S); + + totalLength += bufCount; + } + + private byte[] gHASH(byte[] b) + { + byte[] Y = new byte[16]; + + for (int pos = 0; pos < b.length; pos += 16) + { + byte[] X = new byte[16]; + int num = Math.min(b.length - pos, 16); + System.arraycopy(b, pos, X, 0, num); + xor(Y, X); + multiplier.multiplyH(Y); + } + + return Y; + } + +// private void gHASHBlock(byte[] block) +// { +// xor(S, block); +// multiplier.multiplyH(S); +// } + +// private static void inc(byte[] block) +// { +// for (int i = 15; i >= 12; --i) +// { +// byte b = (byte)((block[i] + 1) & 0xff); +// block[i] = b; +// +// if (b != 0) +// { +// break; +// } +// } +// } + + private static void xor(byte[] block, byte[] val) + { + for (int i = 15; i >= 0; --i) + { + block[i] ^= val[i]; + } + } + + private static void packLength(long count, byte[] bs, int off) + { + Pack.intToBigEndian((int)(count >>> 32), bs, off); + Pack.intToBigEndian((int)count, bs, off + 4); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/GOFBBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/GOFBBlockCipher.java new file mode 100644 index 000000000..41e04f186 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/GOFBBlockCipher.java @@ -0,0 +1,226 @@ +package com.google.bitcoin.bouncycastle.crypto.modes; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +/** + * implements the GOST 28147 OFB counter mode (GCTR). + */ +public class GOFBBlockCipher + implements BlockCipher +{ + private byte[] IV; + private byte[] ofbV; + private byte[] ofbOutV; + + private final int blockSize; + private final BlockCipher cipher; + + boolean firstStep = true; + int N3; + int N4; + static final int C1 = 16843012; //00000001000000010000000100000100 + static final int C2 = 16843009; //00000001000000010000000100000001 + + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of the + * counter mode (must have a 64 bit block size). + */ + public GOFBBlockCipher( + BlockCipher cipher) + { + this.cipher = cipher; + this.blockSize = cipher.getBlockSize(); + + if (blockSize != 8) + { + throw new IllegalArgumentException("GCTR only for 64 bit block ciphers"); + } + + this.IV = new byte[cipher.getBlockSize()]; + this.ofbV = new byte[cipher.getBlockSize()]; + this.ofbOutV = new byte[cipher.getBlockSize()]; + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * Initialise the cipher and, possibly, the initialisation vector (IV). + * If an IV isn't passed as part of the parameter, the IV will be all zeros. + * An IV which is too short is handled in FIPS compliant fashion. + * + * @param encrypting if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, //ignored by this CTR mode + CipherParameters params) + throws IllegalArgumentException + { + firstStep = true; + N3 = 0; + N4 = 0; + + if (params instanceof ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)params; + byte[] iv = ivParam.getIV(); + + if (iv.length < IV.length) + { + // prepend the supplied IV with zeros (per FIPS PUB 81) + System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length); + for (int i = 0; i < IV.length - iv.length; i++) + { + IV[i] = 0; + } + } + else + { + System.arraycopy(iv, 0, IV, 0, IV.length); + } + + reset(); + + cipher.init(true, ivParam.getParameters()); + } + else + { + reset(); + + cipher.init(true, params); + } + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/GCTR" + * and the block size in bits + */ + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/GCTR"; + } + + + /** + * return the block size we are operating at (in bytes). + * + * @return the block size we are operating at (in bytes). + */ + public int getBlockSize() + { + return blockSize; + } + + /** + * Process one block of input from the array in and write it to + * the out array. + * + * @param in the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param out the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (firstStep) + { + firstStep = false; + cipher.processBlock(ofbV, 0, ofbOutV, 0); + N3 = bytesToint(ofbOutV, 0); + N4 = bytesToint(ofbOutV, 4); + } + N3 += C2; + N4 += C1; + intTobytes(N3, ofbV, 0); + intTobytes(N4, ofbV, 4); + + cipher.processBlock(ofbV, 0, ofbOutV, 0); + + // + // XOR the ofbV with the plaintext producing the cipher text (and + // the next input block). + // + for (int i = 0; i < blockSize; i++) + { + out[outOff + i] = (byte)(ofbOutV[i] ^ in[inOff + i]); + } + + // + // change over the input block. + // + System.arraycopy(ofbV, blockSize, ofbV, 0, ofbV.length - blockSize); + System.arraycopy(ofbOutV, 0, ofbV, ofbV.length - blockSize, blockSize); + + return blockSize; + } + + /** + * reset the feedback vector back to the IV and reset the underlying + * cipher. + */ + public void reset() + { + System.arraycopy(IV, 0, ofbV, 0, IV.length); + + cipher.reset(); + } + + //array of bytes to type int + private int bytesToint( + byte[] in, + int inOff) + { + return ((in[inOff + 3] << 24) & 0xff000000) + ((in[inOff + 2] << 16) & 0xff0000) + + ((in[inOff + 1] << 8) & 0xff00) + (in[inOff] & 0xff); + } + + //int to array of bytes + private void intTobytes( + int num, + byte[] out, + int outOff) + { + out[outOff + 3] = (byte)(num >>> 24); + out[outOff + 2] = (byte)(num >>> 16); + out[outOff + 1] = (byte)(num >>> 8); + out[outOff] = (byte)num; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/OFBBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/OFBBlockCipher.java new file mode 100644 index 000000000..369223d07 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/OFBBlockCipher.java @@ -0,0 +1,179 @@ +package com.google.bitcoin.bouncycastle.crypto.modes; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +/** + * implements a Output-FeedBack (OFB) mode on top of a simple cipher. + */ +public class OFBBlockCipher + implements BlockCipher +{ + private byte[] IV; + private byte[] ofbV; + private byte[] ofbOutV; + + private final int blockSize; + private final BlockCipher cipher; + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of the + * feedback mode. + * @param blockSize the block size in bits (note: a multiple of 8) + */ + public OFBBlockCipher( + BlockCipher cipher, + int blockSize) + { + this.cipher = cipher; + this.blockSize = blockSize / 8; + + this.IV = new byte[cipher.getBlockSize()]; + this.ofbV = new byte[cipher.getBlockSize()]; + this.ofbOutV = new byte[cipher.getBlockSize()]; + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * Initialise the cipher and, possibly, the initialisation vector (IV). + * If an IV isn't passed as part of the parameter, the IV will be all zeros. + * An IV which is too short is handled in FIPS compliant fashion. + * + * @param encrypting if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean encrypting, //ignored by this OFB mode + CipherParameters params) + throws IllegalArgumentException + { + if (params instanceof ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)params; + byte[] iv = ivParam.getIV(); + + if (iv.length < IV.length) + { + // prepend the supplied IV with zeros (per FIPS PUB 81) + System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length); + for (int i = 0; i < IV.length - iv.length; i++) + { + IV[i] = 0; + } + } + else + { + System.arraycopy(iv, 0, IV, 0, IV.length); + } + + reset(); + + cipher.init(true, ivParam.getParameters()); + } + else + { + reset(); + + cipher.init(true, params); + } + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/OFB" + * and the block size in bits + */ + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/OFB" + (blockSize * 8); + } + + + /** + * return the block size we are operating at (in bytes). + * + * @return the block size we are operating at (in bytes). + */ + public int getBlockSize() + { + return blockSize; + } + + /** + * Process one block of input from the array in and write it to + * the out array. + * + * @param in the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param out the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + cipher.processBlock(ofbV, 0, ofbOutV, 0); + + // + // XOR the ofbV with the plaintext producing the cipher text (and + // the next input block). + // + for (int i = 0; i < blockSize; i++) + { + out[outOff + i] = (byte)(ofbOutV[i] ^ in[inOff + i]); + } + + // + // change over the input block. + // + System.arraycopy(ofbV, blockSize, ofbV, 0, ofbV.length - blockSize); + System.arraycopy(ofbOutV, 0, ofbV, ofbV.length - blockSize, blockSize); + + return blockSize; + } + + /** + * reset the feedback vector back to the IV and reset the underlying + * cipher. + */ + public void reset() + { + System.arraycopy(IV, 0, ofbV, 0, IV.length); + + cipher.reset(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/OpenPGPCFBBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/OpenPGPCFBBlockCipher.java new file mode 100644 index 000000000..ade52daa9 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/OpenPGPCFBBlockCipher.java @@ -0,0 +1,312 @@ +package com.google.bitcoin.bouncycastle.crypto.modes; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; + +/** + * Implements OpenPGP's rather strange version of Cipher-FeedBack (CFB) mode + * on top of a simple cipher. This class assumes the IV has been prepended + * to the data stream already, and just accomodates the reset after + * (blockSize + 2) bytes have been read. + *

    + * For further info see RFC 2440. + */ +public class OpenPGPCFBBlockCipher + implements BlockCipher +{ + private byte[] IV; + private byte[] FR; + private byte[] FRE; + + private BlockCipher cipher; + + private int count; + private int blockSize; + private boolean forEncryption; + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of the + * feedback mode. + */ + public OpenPGPCFBBlockCipher( + BlockCipher cipher) + { + this.cipher = cipher; + + this.blockSize = cipher.getBlockSize(); + this.IV = new byte[blockSize]; + this.FR = new byte[blockSize]; + this.FRE = new byte[blockSize]; + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/PGPCFB" + * and the block size in bits. + */ + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/OpenPGPCFB"; + } + + /** + * return the block size we are operating at. + * + * @return the block size we are operating at (in bytes). + */ + public int getBlockSize() + { + return cipher.getBlockSize(); + } + + /** + * Process one block of input from the array in and write it to + * the out array. + * + * @param in the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param out the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + return (forEncryption) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff); + } + + /** + * reset the chaining vector back to the IV and reset the underlying + * cipher. + */ + public void reset() + { + count = 0; + + System.arraycopy(IV, 0, FR, 0, FR.length); + + cipher.reset(); + } + + /** + * Initialise the cipher and, possibly, the initialisation vector (IV). + * If an IV isn't passed as part of the parameter, the IV will be all zeros. + * An IV which is too short is handled in FIPS compliant fashion. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + throws IllegalArgumentException + { + this.forEncryption = forEncryption; + + reset(); + + cipher.init(true, params); + } + + /** + * Encrypt one byte of data according to CFB mode. + * @param data the byte to encrypt + * @param blockOff offset in the current block + * @return the encrypted byte + */ + private byte encryptByte(byte data, int blockOff) + { + return (byte)(FRE[blockOff] ^ data); + } + + /** + * Do the appropriate processing for CFB IV mode encryption. + * + * @param in the array containing the data to be encrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (count > blockSize) + { + FR[blockSize - 2] = out[outOff] = encryptByte(in[inOff], blockSize - 2); + FR[blockSize - 1] = out[outOff + 1] = encryptByte(in[inOff + 1], blockSize - 1); + + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 2; n < blockSize; n++) + { + FR[n - 2] = out[outOff + n] = encryptByte(in[inOff + n], n - 2); + } + } + else if (count == 0) + { + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 0; n < blockSize; n++) + { + FR[n] = out[outOff + n] = encryptByte(in[inOff + n], n); + } + + count += blockSize; + } + else if (count == blockSize) + { + cipher.processBlock(FR, 0, FRE, 0); + + out[outOff] = encryptByte(in[inOff], 0); + out[outOff + 1] = encryptByte(in[inOff + 1], 1); + + // + // do reset + // + System.arraycopy(FR, 2, FR, 0, blockSize - 2); + System.arraycopy(out, outOff, FR, blockSize - 2, 2); + + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 2; n < blockSize; n++) + { + FR[n - 2] = out[outOff + n] = encryptByte(in[inOff + n], n - 2); + } + + count += blockSize; + } + + return blockSize; + } + + /** + * Do the appropriate processing for CFB IV mode decryption. + * + * @param in the array containing the data to be decrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (count > blockSize) + { + byte inVal = in[inOff]; + FR[blockSize - 2] = inVal; + out[outOff] = encryptByte(inVal, blockSize - 2); + + inVal = in[inOff + 1]; + FR[blockSize - 1] = inVal; + out[outOff + 1] = encryptByte(inVal, blockSize - 1); + + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 2; n < blockSize; n++) + { + inVal = in[inOff + n]; + FR[n - 2] = inVal; + out[outOff + n] = encryptByte(inVal, n - 2); + } + } + else if (count == 0) + { + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 0; n < blockSize; n++) + { + FR[n] = in[inOff + n]; + out[n] = encryptByte(in[inOff + n], n); + } + + count += blockSize; + } + else if (count == blockSize) + { + cipher.processBlock(FR, 0, FRE, 0); + + byte inVal1 = in[inOff]; + byte inVal2 = in[inOff + 1]; + out[outOff ] = encryptByte(inVal1, 0); + out[outOff + 1] = encryptByte(inVal2, 1); + + System.arraycopy(FR, 2, FR, 0, blockSize - 2); + + FR[blockSize - 2] = inVal1; + FR[blockSize - 1] = inVal2; + + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 2; n < blockSize; n++) + { + byte inVal = in[inOff + n]; + FR[n - 2] = inVal; + out[outOff + n] = encryptByte(inVal, n - 2); + } + + count += blockSize; + } + + return blockSize; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/PGPCFBBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/PGPCFBBlockCipher.java new file mode 100644 index 000000000..01d6f9f83 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/PGPCFBBlockCipher.java @@ -0,0 +1,450 @@ +package com.google.bitcoin.bouncycastle.crypto.modes; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Implements OpenPGP's rather strange version of Cipher-FeedBack (CFB) mode on top of a simple cipher. For further info see RFC 2440. + */ +public class PGPCFBBlockCipher + implements BlockCipher +{ + private byte[] IV; + private byte[] FR; + private byte[] FRE; + private byte[] tmp; + + private BlockCipher cipher; + + private int count; + private int blockSize; + private boolean forEncryption; + + private boolean inlineIv; // if false we don't need to prepend an IV + + /** + * Basic constructor. + * + * @param cipher the block cipher to be used as the basis of the + * feedback mode. + * @param inlineIv if true this is for PGP CFB with a prepended iv. + */ + public PGPCFBBlockCipher( + BlockCipher cipher, + boolean inlineIv) + { + this.cipher = cipher; + this.inlineIv = inlineIv; + + this.blockSize = cipher.getBlockSize(); + this.IV = new byte[blockSize]; + this.FR = new byte[blockSize]; + this.FRE = new byte[blockSize]; + this.tmp = new byte[blockSize]; + } + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + /** + * return the algorithm name and mode. + * + * @return the name of the underlying algorithm followed by "/PGPCFB" + * and the block size in bits. + */ + public String getAlgorithmName() + { + if (inlineIv) + { + return cipher.getAlgorithmName() + "/PGPCFBwithIV"; + } + else + { + return cipher.getAlgorithmName() + "/PGPCFB"; + } + } + + /** + * return the block size we are operating at. + * + * @return the block size we are operating at (in bytes). + */ + public int getBlockSize() + { + return cipher.getBlockSize(); + } + + /** + * Process one block of input from the array in and write it to + * the out array. + * + * @param in the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param out the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if (inlineIv) + { + return (forEncryption) ? encryptBlockWithIV(in, inOff, out, outOff) : decryptBlockWithIV(in, inOff, out, outOff); + } + else + { + return (forEncryption) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff); + } + } + + /** + * reset the chaining vector back to the IV and reset the underlying + * cipher. + */ + public void reset() + { + count = 0; + + for (int i = 0; i != FR.length; i++) + { + if (inlineIv) + { + FR[i] = 0; + } + else + { + FR[i] = IV[i]; // if simple mode, key is IV (even if this is zero) + } + } + + cipher.reset(); + } + + /** + * Initialise the cipher and, possibly, the initialisation vector (IV). + * If an IV isn't passed as part of the parameter, the IV will be all zeros. + * An IV which is too short is handled in FIPS compliant fashion. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + throws IllegalArgumentException + { + this.forEncryption = forEncryption; + + if (params instanceof ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)params; + byte[] iv = ivParam.getIV(); + + if (iv.length < IV.length) + { + // prepend the supplied IV with zeros (per FIPS PUB 81) + System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length); + for (int i = 0; i < IV.length - iv.length; i++) + { + IV[i] = 0; + } + } + else + { + System.arraycopy(iv, 0, IV, 0, IV.length); + } + + reset(); + + cipher.init(true, ivParam.getParameters()); + } + else + { + reset(); + + cipher.init(true, params); + } + } + + /** + * Encrypt one byte of data according to CFB mode. + * @param data the byte to encrypt + * @param where am i in the current block, determines when to resync the block + * @returns the encrypted byte + */ + private byte encryptByte(byte data, int blockOff) + { + return (byte)(FRE[blockOff] ^ data); + } + + /** + * Do the appropriate processing for CFB IV mode encryption. + * + * @param in the array containing the data to be encrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int encryptBlockWithIV( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (count == 0) + { + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 0; n < blockSize; n++) + { + out[outOff + n] = encryptByte(IV[n], n); + } + + System.arraycopy(out, outOff, FR, 0, blockSize); + + cipher.processBlock(FR, 0, FRE, 0); + + out[outOff + blockSize] = encryptByte(IV[blockSize - 2], 0); + out[outOff + blockSize + 1] = encryptByte(IV[blockSize - 1], 1); + + System.arraycopy(out, outOff + 2, FR, 0, blockSize); + + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 0; n < blockSize; n++) + { + out[outOff + blockSize + 2 + n] = encryptByte(in[inOff + n], n); + } + + System.arraycopy(out, outOff + blockSize + 2, FR, 0, blockSize); + + count += 2 * blockSize + 2; + + return 2 * blockSize + 2; + } + else if (count >= blockSize + 2) + { + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 0; n < blockSize; n++) + { + out[outOff + n] = encryptByte(in[inOff + n], n); + } + + System.arraycopy(out, outOff, FR, 0, blockSize); + } + + return blockSize; + } + + /** + * Do the appropriate processing for CFB IV mode decryption. + * + * @param in the array containing the data to be decrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int decryptBlockWithIV( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + if (count == 0) + { + for (int n = 0; n < blockSize; n++) + { + FR[n] = in[inOff + n]; + } + + cipher.processBlock(FR, 0, FRE, 0); + + count += blockSize; + + return 0; + } + else if (count == blockSize) + { + // copy in buffer so that this mode works if in and out are the same + System.arraycopy(in, inOff, tmp, 0, blockSize); + + System.arraycopy(FR, 2, FR, 0, blockSize - 2); + + FR[blockSize - 2] = tmp[0]; + FR[blockSize - 1] = tmp[1]; + + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 0; n < blockSize - 2; n++) + { + out[outOff + n] = encryptByte(tmp[n + 2], n); + } + + System.arraycopy(tmp, 2, FR, 0, blockSize - 2); + + count += 2; + + return blockSize - 2; + } + else if (count >= blockSize + 2) + { + // copy in buffer so that this mode works if in and out are the same + System.arraycopy(in, inOff, tmp, 0, blockSize); + + out[outOff + 0] = encryptByte(tmp[0], blockSize - 2); + out[outOff + 1] = encryptByte(tmp[1], blockSize - 1); + + System.arraycopy(tmp, 0, FR, blockSize - 2, 2); + + cipher.processBlock(FR, 0, FRE, 0); + + for (int n = 0; n < blockSize - 2; n++) + { + out[outOff + n + 2] = encryptByte(tmp[n + 2], n); + } + + System.arraycopy(tmp, 2, FR, 0, blockSize - 2); + + } + + return blockSize; + } + + /** + * Do the appropriate processing for CFB mode encryption. + * + * @param in the array containing the data to be encrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int encryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + cipher.processBlock(FR, 0, FRE, 0); + for (int n = 0; n < blockSize; n++) + { + out[outOff + n] = encryptByte(in[inOff + n], n); + } + + for (int n = 0; n < blockSize; n++) + { + FR[n] = out[outOff + n]; + } + + return blockSize; + + } + + /** + * Do the appropriate processing for CFB mode decryption. + * + * @param in the array containing the data to be decrypted. + * @param inOff offset into the in array the data starts at. + * @param out the array the encrypted data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + private int decryptBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if ((inOff + blockSize) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + cipher.processBlock(FR, 0, FRE, 0); + for (int n = 0; n < blockSize; n++) + { + out[outOff + n] = encryptByte(in[inOff + n], n); + } + + for (int n = 0; n < blockSize; n++) + { + FR[n] = in[inOff + n]; + } + + return blockSize; + + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/PaddedBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/PaddedBlockCipher.java new file mode 100644 index 000000000..6856d06ba --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/PaddedBlockCipher.java @@ -0,0 +1,253 @@ +package com.google.bitcoin.bouncycastle.crypto.modes; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.BufferedBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A wrapper class that allows block ciphers to be used to process data in + * a piecemeal fashion with PKCS5/PKCS7 padding. The PaddedBlockCipher + * outputs a block only when the buffer is full and more data is being added, + * or on a doFinal (unless the current block in the buffer is a pad block). + * The padding mechanism used is the one outlined in PKCS5/PKCS7. + * + * @deprecated use org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher instead. + */ +public class PaddedBlockCipher + extends BufferedBlockCipher +{ + /** + * Create a buffered block cipher with, or without, padding. + * + * @param cipher the underlying block cipher this buffering object wraps. + */ + public PaddedBlockCipher( + BlockCipher cipher) + { + this.cipher = cipher; + + buf = new byte[cipher.getBlockSize()]; + bufOff = 0; + } + + /** + * return the size of the output buffer required for an update plus a + * doFinal with an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update and doFinal + * with len bytes of input. + */ + public int getOutputSize( + int len) + { + int total = len + bufOff; + int leftOver = total % buf.length; + + if (leftOver == 0) + { + if (forEncryption) + { + return total + buf.length; + } + + return total; + } + + return total - leftOver + buf.length; + } + + /** + * return the size of the output buffer required for an update + * an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update + * with len bytes of input. + */ + public int getUpdateOutputSize( + int len) + { + int total = len + bufOff; + int leftOver = total % buf.length; + + if (leftOver == 0) + { + return total - buf.length; + } + + return total - leftOver; + } + + /** + * process a single byte, producing an output block if neccessary. + * + * @param in the input byte. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processByte( + byte in, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + int resultLen = 0; + + if (bufOff == buf.length) + { + resultLen = cipher.processBlock(buf, 0, out, outOff); + bufOff = 0; + } + + buf[bufOff++] = in; + + return resultLen; + } + + /** + * process an array of bytes, producing output if necessary. + * + * @param in the input byte array. + * @param inOff the offset at which the input data starts. + * @param len the number of bytes to be copied out of the input array. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = getBlockSize(); + int length = getUpdateOutputSize(len); + + if (length > 0) + { + if ((outOff + length) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + } + + int resultLen = 0; + int gapLen = buf.length - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += cipher.processBlock(buf, 0, out, outOff); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > buf.length) + { + resultLen += cipher.processBlock(in, inOff, out, outOff + resultLen); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + + return resultLen; + } + + /** + * Process the last block in the buffer. If the buffer is currently + * full and padding needs to be added a call to doFinal will produce + * 2 * getBlockSize() bytes. + * + * @param out the array the block currently being held is copied into. + * @param outOff the offset at which the copying starts. + * @exception DataLengthException if there is insufficient space in out for + * the output or we are decrypting and the input is not block size aligned. + * @exception IllegalStateException if the underlying cipher is not + * initialised. + * @exception InvalidCipherTextException if padding is expected and not found. + */ + public int doFinal( + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException, InvalidCipherTextException + { + int blockSize = cipher.getBlockSize(); + int resultLen = 0; + + if (forEncryption) + { + if (bufOff == blockSize) + { + if ((outOff + 2 * blockSize) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + resultLen = cipher.processBlock(buf, 0, out, outOff); + bufOff = 0; + } + + // + // add PKCS7 padding + // + byte code = (byte)(blockSize - bufOff); + + while (bufOff < blockSize) + { + buf[bufOff] = code; + bufOff++; + } + + resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen); + } + else + { + if (bufOff == blockSize) + { + resultLen = cipher.processBlock(buf, 0, buf, 0); + bufOff = 0; + } + else + { + throw new DataLengthException("last block incomplete in decryption"); + } + + // + // remove PKCS7 padding + // + int count = buf[blockSize - 1] & 0xff; + + if ((count < 0) || (count > blockSize)) + { + throw new InvalidCipherTextException("pad block corrupted"); + } + + resultLen -= count; + + System.arraycopy(buf, 0, out, outOff, resultLen); + } + + reset(); + + return resultLen; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/SICBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/SICBlockCipher.java new file mode 100644 index 000000000..9dac24a29 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/SICBlockCipher.java @@ -0,0 +1,119 @@ +package com.google.bitcoin.bouncycastle.crypto.modes; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +/** + * Implements the Segmented Integer Counter (SIC) mode on top of a simple + * block cipher. This mode is also known as CTR mode. + */ +public class SICBlockCipher implements BlockCipher +{ + private final BlockCipher cipher; + private final int blockSize; + + private byte[] IV; + private byte[] counter; + private byte[] counterOut; + + + /** + * Basic constructor. + * + * @param c the block cipher to be used. + */ + public SICBlockCipher(BlockCipher c) + { + this.cipher = c; + this.blockSize = cipher.getBlockSize(); + this.IV = new byte[blockSize]; + this.counter = new byte[blockSize]; + this.counterOut = new byte[blockSize]; + } + + + /** + * return the underlying block cipher that we are wrapping. + * + * @return the underlying block cipher that we are wrapping. + */ + public BlockCipher getUnderlyingCipher() + { + return cipher; + } + + + public void init( + boolean forEncryption, //ignored by this CTR mode + CipherParameters params) + throws IllegalArgumentException + { + if (params instanceof ParametersWithIV) + { + ParametersWithIV ivParam = (ParametersWithIV)params; + byte[] iv = ivParam.getIV(); + System.arraycopy(iv, 0, IV, 0, IV.length); + + reset(); + cipher.init(true, ivParam.getParameters()); + } + else + { + throw new IllegalArgumentException("SIC mode requires ParametersWithIV"); + } + } + + public String getAlgorithmName() + { + return cipher.getAlgorithmName() + "/SIC"; + } + + public int getBlockSize() + { + return cipher.getBlockSize(); + } + + + public int processBlock(byte[] in, int inOff, byte[] out, int outOff) + throws DataLengthException, IllegalStateException + { + cipher.processBlock(counter, 0, counterOut, 0); + + // + // XOR the counterOut with the plaintext producing the cipher text + // + for (int i = 0; i < counterOut.length; i++) + { + out[outOff + i] = (byte)(counterOut[i] ^ in[inOff + i]); + } + + int carry = 1; + + for (int i = counter.length - 1; i >= 0; i--) + { + int x = (counter[i] & 0xff) + carry; + + if (x > 0xff) + { + carry = 1; + } + else + { + carry = 0; + } + + counter[i] = (byte)x; + } + + return counter.length; + } + + + public void reset() + { + System.arraycopy(IV, 0, counter, 0, counter.length); + cipher.reset(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/BasicGCMMultiplier.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/BasicGCMMultiplier.java new file mode 100644 index 000000000..68de02686 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/BasicGCMMultiplier.java @@ -0,0 +1,41 @@ +package com.google.bitcoin.bouncycastle.crypto.modes.gcm; + +import com.google.bitcoin.bouncycastle.util.Arrays; + +public class BasicGCMMultiplier implements GCMMultiplier +{ + private byte[] H; + + public void init(byte[] H) + { + this.H = Arrays.clone(H); + } + + public void multiplyH(byte[] x) + { + byte[] z = new byte[16]; + + for (int i = 0; i < 16; ++i) + { + byte h = H[i]; + for (int j = 7; j >= 0; --j) + { + if ((h & (1 << j)) != 0) + { + GCMUtil.xor(z, x); + } + + boolean lsb = (x[15] & 1) != 0; + GCMUtil.shiftRight(x); + if (lsb) + { + // R = new byte[]{ 0xe1, ... }; +// GCMUtil.xor(v, R); + x[0] ^= (byte)0xe1; + } + } + } + + System.arraycopy(z, 0, x, 0, 16); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/GCMMultiplier.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/GCMMultiplier.java new file mode 100644 index 000000000..d866d9f21 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/GCMMultiplier.java @@ -0,0 +1,7 @@ +package com.google.bitcoin.bouncycastle.crypto.modes.gcm; + +public interface GCMMultiplier +{ + void init(byte[] H); + void multiplyH(byte[] x); +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/GCMUtil.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/GCMUtil.java new file mode 100644 index 000000000..9f794ff7e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/GCMUtil.java @@ -0,0 +1,85 @@ +package com.google.bitcoin.bouncycastle.crypto.modes.gcm; + +import com.google.bitcoin.bouncycastle.crypto.util.Pack; + +abstract class GCMUtil +{ + static int[] asInts(byte[] bs) + { + int[] us = new int[4]; + us[0] = Pack.bigEndianToInt(bs, 0); + us[1] = Pack.bigEndianToInt(bs, 4); + us[2] = Pack.bigEndianToInt(bs, 8); + us[3] = Pack.bigEndianToInt(bs, 12); + return us; + } + + // P is the value with only bit i=1 set + static void multiplyP(int[] x) + { + boolean lsb = (x[3] & 1) != 0; + shiftRight(x); + if (lsb) + { + // R = new int[]{ 0xe1000000, 0, 0, 0 }; +// xor(v, R); + x[0] ^= 0xe1000000; + } + } + + static void multiplyP8(int[] x) + { + for (int i = 8; i != 0; --i) + { + multiplyP(x); + } + } + + static void shiftRight(byte[] block) + { + int i = 0; + int bit = 0; + for (;;) + { + int b = block[i] & 0xff; + block[i] = (byte) ((b >>> 1) | bit); + if (++i == 16) + { + break; + } + bit = (b & 1) << 7; + } + } + + static void shiftRight(int[] block) + { + int i = 0; + int bit = 0; + for (;;) + { + int b = block[i]; + block[i] = (b >>> 1) | bit; + if (++i == 4) + { + break; + } + bit = b << 31; + } + } + + static void xor(byte[] block, byte[] val) + { + for (int i = 15; i >= 0; --i) + { + block[i] ^= val[i]; + } + } + + static void xor(int[] block, int[] val) + { + for (int i = 3; i >= 0; --i) + { + block[i] ^= val[i]; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/Tables64kGCMMultiplier.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/Tables64kGCMMultiplier.java new file mode 100644 index 000000000..35a0f5016 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/Tables64kGCMMultiplier.java @@ -0,0 +1,75 @@ +package com.google.bitcoin.bouncycastle.crypto.modes.gcm; + +import com.google.bitcoin.bouncycastle.crypto.util.Pack; + +public class Tables64kGCMMultiplier + implements GCMMultiplier +{ + private final int[][][] M = new int[16][256][]; + + public void init(byte[] H) + { + M[0][0] = new int[4]; + M[0][128] = GCMUtil.asInts(H); + for (int j = 64; j >= 1; j >>= 1) + { + int[] tmp = new int[4]; + System.arraycopy(M[0][j + j], 0, tmp, 0, 4); + + GCMUtil.multiplyP(tmp); + M[0][j] = tmp; + } + + int i = 0; + for (;;) + { + for (int j = 2; j < 256; j += j) + { + for (int k = 1; k < j; ++k) + { + int[] tmp = new int[4]; + System.arraycopy(M[i][j], 0, tmp, 0, 4); + + GCMUtil.xor(tmp, M[i][k]); + M[i][j + k] = tmp; + } + } + + if (++i == 16) + { + return; + } + + M[i][0] = new int[4]; + for (int j = 128; j > 0; j >>= 1) + { + int[] tmp = new int[4]; + System.arraycopy(M[i - 1][j], 0, tmp, 0, 4); + + GCMUtil.multiplyP8(tmp); + M[i][j] = tmp; + } + } + } + + public void multiplyH(byte[] x) + { +// assert x.Length == 16; + + int[] z = new int[4]; + for (int i = 15; i >= 0; --i) + { +// GCMUtil.xor(z, M[i][x[i] & 0xff]); + int[] m = M[i][x[i] & 0xff]; + z[0] ^= m[0]; + z[1] ^= m[1]; + z[2] ^= m[2]; + z[3] ^= m[3]; + } + + Pack.intToBigEndian(z[0], x, 0); + Pack.intToBigEndian(z[1], x, 4); + Pack.intToBigEndian(z[2], x, 8); + Pack.intToBigEndian(z[3], x, 12); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.java b/src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.java new file mode 100644 index 000000000..9d414ab40 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.java @@ -0,0 +1,102 @@ +package com.google.bitcoin.bouncycastle.crypto.modes.gcm; + +import com.google.bitcoin.bouncycastle.crypto.util.Pack; + +public class Tables8kGCMMultiplier implements GCMMultiplier +{ + private final int[][][] M = new int[32][16][]; + + public void init(byte[] H) + { + M[0][0] = new int[4]; + M[1][0] = new int[4]; + M[1][8] = GCMUtil.asInts(H); + + for (int j = 4; j >= 1; j >>= 1) + { + int[] tmp = new int[4]; + System.arraycopy(M[1][j + j], 0, tmp, 0, 4); + + GCMUtil.multiplyP(tmp); + M[1][j] = tmp; + } + + { + int[] tmp = new int[4]; + System.arraycopy(M[1][1], 0, tmp, 0, 4); + + GCMUtil.multiplyP(tmp); + M[0][8] = tmp; + } + + for (int j = 4; j >= 1; j >>= 1) + { + int[] tmp = new int[4]; + System.arraycopy(M[0][j + j], 0, tmp, 0, 4); + + GCMUtil.multiplyP(tmp); + M[0][j] = tmp; + } + + int i = 0; + for (;;) + { + for (int j = 2; j < 16; j += j) + { + for (int k = 1; k < j; ++k) + { + int[] tmp = new int[4]; + System.arraycopy(M[i][j], 0, tmp, 0, 4); + + GCMUtil.xor(tmp, M[i][k]); + M[i][j + k] = tmp; + } + } + + if (++i == 32) + { + return; + } + + if (i > 1) + { + M[i][0] = new int[4]; + for(int j = 8; j > 0; j >>= 1) + { + int[] tmp = new int[4]; + System.arraycopy(M[i - 2][j], 0, tmp, 0, 4); + + GCMUtil.multiplyP8(tmp); + M[i][j] = tmp; + } + } + } + } + + public void multiplyH(byte[] x) + { +// assert x.Length == 16; + + int[] z = new int[4]; + for (int i = 15; i >= 0; --i) + { +// GCMUtil.xor(z, M[i + i][x[i] & 0x0f]); + int[] m = M[i + i][x[i] & 0x0f]; + z[0] ^= m[0]; + z[1] ^= m[1]; + z[2] ^= m[2]; + z[3] ^= m[3]; +// GCMUtil.xor(z, M[i + i + 1][(x[i] & 0xf0) >>> 4]); + m = M[i + i + 1][(x[i] & 0xf0) >>> 4]; + z[0] ^= m[0]; + z[1] ^= m[1]; + z[2] ^= m[2]; + z[3] ^= m[3]; + } + + Pack.intToBigEndian(z[0], x, 0); + Pack.intToBigEndian(z[1], x, 4); + Pack.intToBigEndian(z[2], x, 8); + Pack.intToBigEndian(z[3], x, 12); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/paddings/BlockCipherPadding.java b/src/com/google/bitcoin/bouncycastle/crypto/paddings/BlockCipherPadding.java new file mode 100644 index 000000000..8d4cd50a1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/paddings/BlockCipherPadding.java @@ -0,0 +1,48 @@ +package com.google.bitcoin.bouncycastle.crypto.paddings; + +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; + +/** + * Block cipher padders are expected to conform to this interface + */ +public interface BlockCipherPadding +{ + /** + * Initialise the padder. + * + * @param random the source of randomness for the padding, if required. + */ + public void init(SecureRandom random) + throws IllegalArgumentException; + + /** + * Return the name of the algorithm the cipher implements. + * + * @return the name of the algorithm the cipher implements. + */ + public String getPaddingName(); + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + *

    + * Note: this assumes that the last block of plain text is always + * passed to it inside in. i.e. if inOff is zero, indicating the + * entire block is to be overwritten with padding the value of in + * should be the same as the last block of plain text. The reason + * for this is that some modes such as "trailing bit compliment" + * base the padding on the last byte of plain text. + *

    + */ + public int addPadding(byte[] in, int inOff); + + /** + * return the number of pad bytes present in the block. + * @exception InvalidCipherTextException if the padding is badly formed + * or invalid. + */ + public int padCount(byte[] in) + throws InvalidCipherTextException; +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/paddings/ISO10126d2Padding.java b/src/com/google/bitcoin/bouncycastle/crypto/paddings/ISO10126d2Padding.java new file mode 100644 index 000000000..5afe8c56b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/paddings/ISO10126d2Padding.java @@ -0,0 +1,79 @@ +package com.google.bitcoin.bouncycastle.crypto.paddings; + +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A padder that adds ISO10126-2 padding to a block. + */ +public class ISO10126d2Padding + implements BlockCipherPadding +{ + SecureRandom random; + + /** + * Initialise the padder. + * + * @param random a SecureRandom if available. + */ + public void init(SecureRandom random) + throws IllegalArgumentException + { + if (random != null) + { + this.random = random; + } + else + { + this.random = new SecureRandom(); + } + } + + /** + * Return the name of the algorithm the padder implements. + * + * @return the name of the algorithm the padder implements. + */ + public String getPaddingName() + { + return "ISO10126-2"; + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + */ + public int addPadding( + byte[] in, + int inOff) + { + byte code = (byte)(in.length - inOff); + + while (inOff < (in.length - 1)) + { + in[inOff] = (byte)random.nextInt(); + inOff++; + } + + in[inOff] = code; + + return code; + } + + /** + * return the number of pad bytes present in the block. + */ + public int padCount(byte[] in) + throws InvalidCipherTextException + { + int count = in[in.length - 1] & 0xff; + + if (count > in.length) + { + throw new InvalidCipherTextException("pad block corrupted"); + } + + return count; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/paddings/ISO7816d4Padding.java b/src/com/google/bitcoin/bouncycastle/crypto/paddings/ISO7816d4Padding.java new file mode 100644 index 000000000..6b963afc7 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/paddings/ISO7816d4Padding.java @@ -0,0 +1,77 @@ +package com.google.bitcoin.bouncycastle.crypto.paddings; + +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A padder that adds the padding according to the scheme referenced in + * ISO 7814-4 - scheme 2 from ISO 9797-1. The first byte is 0x80, rest is 0x00 + */ +public class ISO7816d4Padding + implements BlockCipherPadding +{ + /** + * Initialise the padder. + * + * @param random - a SecureRandom if available. + */ + public void init(SecureRandom random) + throws IllegalArgumentException + { + // nothing to do. + } + + /** + * Return the name of the algorithm the padder implements. + * + * @return the name of the algorithm the padder implements. + */ + public String getPaddingName() + { + return "ISO7816-4"; + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + */ + public int addPadding( + byte[] in, + int inOff) + { + int added = (in.length - inOff); + + in [inOff]= (byte) 0x80; + inOff ++; + + while (inOff < in.length) + { + in[inOff] = (byte) 0; + inOff++; + } + + return added; + } + + /** + * return the number of pad bytes present in the block. + */ + public int padCount(byte[] in) + throws InvalidCipherTextException + { + int count = in.length - 1; + + while (count > 0 && in[count] == 0) + { + count--; + } + + if (in[count] != (byte)0x80) + { + throw new InvalidCipherTextException("pad block corrupted"); + } + + return in.length - count; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/paddings/PKCS7Padding.java b/src/com/google/bitcoin/bouncycastle/crypto/paddings/PKCS7Padding.java new file mode 100644 index 000000000..dc58138fb --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/paddings/PKCS7Padding.java @@ -0,0 +1,76 @@ +package com.google.bitcoin.bouncycastle.crypto.paddings; + +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A padder that adds PKCS7/PKCS5 padding to a block. + */ +public class PKCS7Padding + implements BlockCipherPadding +{ + /** + * Initialise the padder. + * + * @param random - a SecureRandom if available. + */ + public void init(SecureRandom random) + throws IllegalArgumentException + { + // nothing to do. + } + + /** + * Return the name of the algorithm the padder implements. + * + * @return the name of the algorithm the padder implements. + */ + public String getPaddingName() + { + return "PKCS7"; + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + */ + public int addPadding( + byte[] in, + int inOff) + { + byte code = (byte)(in.length - inOff); + + while (inOff < in.length) + { + in[inOff] = code; + inOff++; + } + + return code; + } + + /** + * return the number of pad bytes present in the block. + */ + public int padCount(byte[] in) + throws InvalidCipherTextException + { + int count = in[in.length - 1] & 0xff; + + if (count > in.length || count == 0) + { + throw new InvalidCipherTextException("pad block corrupted"); + } + + for (int i = 1; i <= count; i++) + { + if (in[in.length - i] != count) + { + throw new InvalidCipherTextException("pad block corrupted"); + } + } + + return count; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher.java b/src/com/google/bitcoin/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher.java new file mode 100644 index 000000000..a0428983f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/paddings/PaddedBufferedBlockCipher.java @@ -0,0 +1,298 @@ +package com.google.bitcoin.bouncycastle.crypto.paddings; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.BufferedBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; + +/** + * A wrapper class that allows block ciphers to be used to process data in + * a piecemeal fashion with padding. The PaddedBufferedBlockCipher + * outputs a block only when the buffer is full and more data is being added, + * or on a doFinal (unless the current block in the buffer is a pad block). + * The default padding mechanism used is the one outlined in PKCS5/PKCS7. + */ +public class PaddedBufferedBlockCipher + extends BufferedBlockCipher +{ + BlockCipherPadding padding; + + /** + * Create a buffered block cipher with the desired padding. + * + * @param cipher the underlying block cipher this buffering object wraps. + * @param padding the padding type. + */ + public PaddedBufferedBlockCipher( + BlockCipher cipher, + BlockCipherPadding padding) + { + this.cipher = cipher; + this.padding = padding; + + buf = new byte[cipher.getBlockSize()]; + bufOff = 0; + } + + /** + * Create a buffered block cipher PKCS7 padding + * + * @param cipher the underlying block cipher this buffering object wraps. + */ + public PaddedBufferedBlockCipher( + BlockCipher cipher) + { + this(cipher, new PKCS7Padding()); + } + + /** + * initialise the cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param params the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params) + throws IllegalArgumentException + { + this.forEncryption = forEncryption; + + reset(); + + if (params instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)params; + + padding.init(p.getRandom()); + + cipher.init(forEncryption, p.getParameters()); + } + else + { + padding.init(null); + + cipher.init(forEncryption, params); + } + } + + /** + * return the minimum size of the output buffer required for an update + * plus a doFinal with an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update and doFinal + * with len bytes of input. + */ + public int getOutputSize( + int len) + { + int total = len + bufOff; + int leftOver = total % buf.length; + + if (leftOver == 0) + { + if (forEncryption) + { + return total + buf.length; + } + + return total; + } + + return total - leftOver + buf.length; + } + + /** + * return the size of the output buffer required for an update + * an input of len bytes. + * + * @param len the length of the input. + * @return the space required to accommodate a call to update + * with len bytes of input. + */ + public int getUpdateOutputSize( + int len) + { + int total = len + bufOff; + int leftOver = total % buf.length; + + if (leftOver == 0) + { + return total - buf.length; + } + + return total - leftOver; + } + + /** + * process a single byte, producing an output block if neccessary. + * + * @param in the input byte. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processByte( + byte in, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + int resultLen = 0; + + if (bufOff == buf.length) + { + resultLen = cipher.processBlock(buf, 0, out, outOff); + bufOff = 0; + } + + buf[bufOff++] = in; + + return resultLen; + } + + /** + * process an array of bytes, producing output if necessary. + * + * @param in the input byte array. + * @param inOff the offset at which the input data starts. + * @param len the number of bytes to be copied out of the input array. + * @param out the space for any output that might be produced. + * @param outOff the offset from which the output will be copied. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there isn't enough space in out. + * @exception IllegalStateException if the cipher isn't initialised. + */ + public int processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int blockSize = getBlockSize(); + int length = getUpdateOutputSize(len); + + if (length > 0) + { + if ((outOff + length) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + } + + int resultLen = 0; + int gapLen = buf.length - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += cipher.processBlock(buf, 0, out, outOff); + + bufOff = 0; + len -= gapLen; + inOff += gapLen; + + while (len > buf.length) + { + resultLen += cipher.processBlock(in, inOff, out, outOff + resultLen); + + len -= blockSize; + inOff += blockSize; + } + } + + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + + return resultLen; + } + + /** + * Process the last block in the buffer. If the buffer is currently + * full and padding needs to be added a call to doFinal will produce + * 2 * getBlockSize() bytes. + * + * @param out the array the block currently being held is copied into. + * @param outOff the offset at which the copying starts. + * @return the number of output bytes copied to out. + * @exception DataLengthException if there is insufficient space in out for + * the output or we are decrypting and the input is not block size aligned. + * @exception IllegalStateException if the underlying cipher is not + * initialised. + * @exception InvalidCipherTextException if padding is expected and not found. + */ + public int doFinal( + byte[] out, + int outOff) + throws DataLengthException, IllegalStateException, InvalidCipherTextException + { + int blockSize = cipher.getBlockSize(); + int resultLen = 0; + + if (forEncryption) + { + if (bufOff == blockSize) + { + if ((outOff + 2 * blockSize) > out.length) + { + reset(); + + throw new DataLengthException("output buffer too short"); + } + + resultLen = cipher.processBlock(buf, 0, out, outOff); + bufOff = 0; + } + + padding.addPadding(buf, bufOff); + + resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen); + + reset(); + } + else + { + if (bufOff == blockSize) + { + resultLen = cipher.processBlock(buf, 0, buf, 0); + bufOff = 0; + } + else + { + reset(); + + throw new DataLengthException("last block incomplete in decryption"); + } + + try + { + resultLen -= padding.padCount(buf); + + System.arraycopy(buf, 0, out, outOff, resultLen); + } + finally + { + reset(); + } + } + + return resultLen; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/paddings/TBCPadding.java b/src/com/google/bitcoin/bouncycastle/crypto/paddings/TBCPadding.java new file mode 100644 index 000000000..6bc1167f6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/paddings/TBCPadding.java @@ -0,0 +1,89 @@ +package com.google.bitcoin.bouncycastle.crypto.paddings; + +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A padder that adds Trailing-Bit-Compliment padding to a block. + *

    + * This padding pads the block out with the compliment of the last bit + * of the plain text. + *

    + */ +public class TBCPadding + implements BlockCipherPadding +{ + /** + * Initialise the padder. + * + * @param random - a SecureRandom if available. + */ + public void init(SecureRandom random) + throws IllegalArgumentException + { + // nothing to do. + } + + /** + * Return the name of the algorithm the padder implements. + * + * @return the name of the algorithm the padder implements. + */ + public String getPaddingName() + { + return "TBC"; + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + *

    + * Note: this assumes that the last block of plain text is always + * passed to it inside in. i.e. if inOff is zero, indicating the + * entire block is to be overwritten with padding the value of in + * should be the same as the last block of plain text. + *

    + */ + public int addPadding( + byte[] in, + int inOff) + { + int count = in.length - inOff; + byte code; + + if (inOff > 0) + { + code = (byte)((in[inOff - 1] & 0x01) == 0 ? 0xff : 0x00); + } + else + { + code = (byte)((in[in.length - 1] & 0x01) == 0 ? 0xff : 0x00); + } + + while (inOff < in.length) + { + in[inOff] = code; + inOff++; + } + + return count; + } + + /** + * return the number of pad bytes present in the block. + */ + public int padCount(byte[] in) + throws InvalidCipherTextException + { + byte code = in[in.length - 1]; + + int index = in.length - 1; + while (index > 0 && in[index - 1] == code) + { + index--; + } + + return in.length - index; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/paddings/X923Padding.java b/src/com/google/bitcoin/bouncycastle/crypto/paddings/X923Padding.java new file mode 100644 index 000000000..e267e4aee --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/paddings/X923Padding.java @@ -0,0 +1,80 @@ +package com.google.bitcoin.bouncycastle.crypto.paddings; + +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A padder that adds X9.23 padding to a block - if a SecureRandom is + * passed in random padding is assumed, otherwise padding with zeros is used. + */ +public class X923Padding + implements BlockCipherPadding +{ + SecureRandom random = null; + + /** + * Initialise the padder. + * + * @param random a SecureRandom if one is available. + */ + public void init(SecureRandom random) + throws IllegalArgumentException + { + this.random = random; + } + + /** + * Return the name of the algorithm the padder implements. + * + * @return the name of the algorithm the padder implements. + */ + public String getPaddingName() + { + return "X9.23"; + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + */ + public int addPadding( + byte[] in, + int inOff) + { + byte code = (byte)(in.length - inOff); + + while (inOff < in.length - 1) + { + if (random == null) + { + in[inOff] = 0; + } + else + { + in[inOff] = (byte)random.nextInt(); + } + inOff++; + } + + in[inOff] = code; + + return code; + } + + /** + * return the number of pad bytes present in the block. + */ + public int padCount(byte[] in) + throws InvalidCipherTextException + { + int count = in[in.length - 1] & 0xff; + + if (count > in.length) + { + throw new InvalidCipherTextException("pad block corrupted"); + } + + return count; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/paddings/ZeroBytePadding.java b/src/com/google/bitcoin/bouncycastle/crypto/paddings/ZeroBytePadding.java new file mode 100644 index 000000000..e458f91ee --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/paddings/ZeroBytePadding.java @@ -0,0 +1,73 @@ +package com.google.bitcoin.bouncycastle.crypto.paddings; + +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; + +/** + * A padder that adds NULL byte padding to a block. + */ +public class ZeroBytePadding + implements BlockCipherPadding +{ + /** + * Initialise the padder. + * + * @param random - a SecureRandom if available. + */ + public void init(SecureRandom random) + throws IllegalArgumentException + { + // nothing to do. + } + + /** + * Return the name of the algorithm the padder implements. + * + * @return the name of the algorithm the padder implements. + */ + public String getPaddingName() + { + return "ZeroByte"; + } + + /** + * add the pad bytes to the passed in block, returning the + * number of bytes added. + */ + public int addPadding( + byte[] in, + int inOff) + { + int added = (in.length - inOff); + + while (inOff < in.length) + { + in[inOff] = (byte) 0; + inOff++; + } + + return added; + } + + /** + * return the number of pad bytes present in the block. + */ + public int padCount(byte[] in) + throws InvalidCipherTextException + { + int count = in.length; + + while (count > 0) + { + if (in[count - 1] != 0) + { + break; + } + + count--; + } + + return in.length - count; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/AEADParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/AEADParameters.java new file mode 100644 index 000000000..b2d15ab22 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/AEADParameters.java @@ -0,0 +1,48 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +public class AEADParameters + implements CipherParameters +{ + private byte[] associatedText; + private byte[] nonce; + private KeyParameter key; + private int macSize; + + /** + * Base constructor. + * + * @param key key to be used by underlying cipher + * @param macSize macSize in bits + * @param nonce nonce to be used + * @param associatedText associated text, if any + */ + public AEADParameters(KeyParameter key, int macSize, byte[] nonce, byte[] associatedText) + { + this.key = key; + this.nonce = nonce; + this.macSize = macSize; + this.associatedText = associatedText; + } + + public KeyParameter getKey() + { + return key; + } + + public int getMacSize() + { + return macSize; + } + + public byte[] getAssociatedText() + { + return associatedText; + } + + public byte[] getNonce() + { + return nonce; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/AsymmetricKeyParameter.java b/src/com/google/bitcoin/bouncycastle/crypto/params/AsymmetricKeyParameter.java new file mode 100644 index 000000000..844410bc9 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/AsymmetricKeyParameter.java @@ -0,0 +1,20 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +public class AsymmetricKeyParameter + implements CipherParameters +{ + boolean privateKey; + + public AsymmetricKeyParameter( + boolean privateKey) + { + this.privateKey = privateKey; + } + + public boolean isPrivate() + { + return privateKey; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/CCMParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/CCMParameters.java new file mode 100644 index 000000000..299d08c18 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/CCMParameters.java @@ -0,0 +1,18 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +public class CCMParameters + extends AEADParameters +{ + /** + * Base constructor. + * + * @param key key to be used by underlying cipher + * @param macSize macSize in bits + * @param nonce nonce to be used + * @param associatedText associated text, if any + */ + public CCMParameters(KeyParameter key, int macSize, byte[] nonce, byte[] associatedText) + { + super(key, macSize, nonce, associatedText); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/DESParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/DESParameters.java new file mode 100644 index 000000000..4a07a9deb --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/DESParameters.java @@ -0,0 +1,107 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +public class DESParameters + extends KeyParameter +{ + public DESParameters( + byte[] key) + { + super(key); + + if (isWeakKey(key, 0)) + { + throw new IllegalArgumentException("attempt to create weak DES key"); + } + } + + /* + * DES Key length in bytes. + */ + static public final int DES_KEY_LENGTH = 8; + + /* + * Table of weak and semi-weak keys taken from Schneier pp281 + */ + static private final int N_DES_WEAK_KEYS = 16; + + static private byte[] DES_weak_keys = + { + /* weak keys */ + (byte)0x01,(byte)0x01,(byte)0x01,(byte)0x01, (byte)0x01,(byte)0x01,(byte)0x01,(byte)0x01, + (byte)0x1f,(byte)0x1f,(byte)0x1f,(byte)0x1f, (byte)0x0e,(byte)0x0e,(byte)0x0e,(byte)0x0e, + (byte)0xe0,(byte)0xe0,(byte)0xe0,(byte)0xe0, (byte)0xf1,(byte)0xf1,(byte)0xf1,(byte)0xf1, + (byte)0xfe,(byte)0xfe,(byte)0xfe,(byte)0xfe, (byte)0xfe,(byte)0xfe,(byte)0xfe,(byte)0xfe, + + /* semi-weak keys */ + (byte)0x01,(byte)0xfe,(byte)0x01,(byte)0xfe, (byte)0x01,(byte)0xfe,(byte)0x01,(byte)0xfe, + (byte)0x1f,(byte)0xe0,(byte)0x1f,(byte)0xe0, (byte)0x0e,(byte)0xf1,(byte)0x0e,(byte)0xf1, + (byte)0x01,(byte)0xe0,(byte)0x01,(byte)0xe0, (byte)0x01,(byte)0xf1,(byte)0x01,(byte)0xf1, + (byte)0x1f,(byte)0xfe,(byte)0x1f,(byte)0xfe, (byte)0x0e,(byte)0xfe,(byte)0x0e,(byte)0xfe, + (byte)0x01,(byte)0x1f,(byte)0x01,(byte)0x1f, (byte)0x01,(byte)0x0e,(byte)0x01,(byte)0x0e, + (byte)0xe0,(byte)0xfe,(byte)0xe0,(byte)0xfe, (byte)0xf1,(byte)0xfe,(byte)0xf1,(byte)0xfe, + (byte)0xfe,(byte)0x01,(byte)0xfe,(byte)0x01, (byte)0xfe,(byte)0x01,(byte)0xfe,(byte)0x01, + (byte)0xe0,(byte)0x1f,(byte)0xe0,(byte)0x1f, (byte)0xf1,(byte)0x0e,(byte)0xf1,(byte)0x0e, + (byte)0xe0,(byte)0x01,(byte)0xe0,(byte)0x01, (byte)0xf1,(byte)0x01,(byte)0xf1,(byte)0x01, + (byte)0xfe,(byte)0x1f,(byte)0xfe,(byte)0x1f, (byte)0xfe,(byte)0x0e,(byte)0xfe,(byte)0x0e, + (byte)0x1f,(byte)0x01,(byte)0x1f,(byte)0x01, (byte)0x0e,(byte)0x01,(byte)0x0e,(byte)0x01, + (byte)0xfe,(byte)0xe0,(byte)0xfe,(byte)0xe0, (byte)0xfe,(byte)0xf1,(byte)0xfe,(byte)0xf1 + }; + + /** + * DES has 16 weak keys. This method will check + * if the given DES key material is weak or semi-weak. + * Key material that is too short is regarded as weak. + *

    + * See "Applied + * Cryptography" by Bruce Schneier for more information. + * + * @return true if the given DES key material is weak or semi-weak, + * false otherwise. + */ + public static boolean isWeakKey( + byte[] key, + int offset) + { + if (key.length - offset < DES_KEY_LENGTH) + { + throw new IllegalArgumentException("key material too short."); + } + + nextkey: for (int i = 0; i < N_DES_WEAK_KEYS; i++) + { + for (int j = 0; j < DES_KEY_LENGTH; j++) + { + if (key[j + offset] != DES_weak_keys[i * DES_KEY_LENGTH + j]) + { + continue nextkey; + } + } + + return true; + } + return false; + } + + /** + * DES Keys use the LSB as the odd parity bit. This can + * be used to check for corrupt keys. + * + * @param bytes the byte array to set the parity on. + */ + public static void setOddParity( + byte[] bytes) + { + for (int i = 0; i < bytes.length; i++) + { + int b = bytes[i]; + bytes[i] = (byte)((b & 0xfe) | + ((((b >> 1) ^ + (b >> 2) ^ + (b >> 3) ^ + (b >> 4) ^ + (b >> 5) ^ + (b >> 6) ^ + (b >> 7)) ^ 0x01) & 0x01)); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/DESedeParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/DESedeParameters.java new file mode 100644 index 000000000..b03ee33fa --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/DESedeParameters.java @@ -0,0 +1,57 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +public class DESedeParameters + extends DESParameters +{ + /* + * DES-EDE Key length in bytes. + */ + static public final int DES_EDE_KEY_LENGTH = 24; + + public DESedeParameters( + byte[] key) + { + super(key); + + if (isWeakKey(key, 0, key.length)) + { + throw new IllegalArgumentException("attempt to create weak DESede key"); + } + } + + /** + * return true if the passed in key is a DES-EDE weak key. + * + * @param key bytes making up the key + * @param offset offset into the byte array the key starts at + * @param length number of bytes making up the key + */ + public static boolean isWeakKey( + byte[] key, + int offset, + int length) + { + for (int i = offset; i < length; i += DES_KEY_LENGTH) + { + if (DESParameters.isWeakKey(key, i)) + { + return true; + } + } + + return false; + } + + /** + * return true if the passed in key is a DES-EDE weak key. + * + * @param key bytes making up the key + * @param offset offset into the byte array the key starts at + */ + public static boolean isWeakKey( + byte[] key, + int offset) + { + return isWeakKey(key, offset, key.length - offset); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/DHKeyGenerationParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/DHKeyGenerationParameters.java new file mode 100644 index 000000000..b5c497740 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/DHKeyGenerationParameters.java @@ -0,0 +1,30 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; + +public class DHKeyGenerationParameters + extends KeyGenerationParameters +{ + private DHParameters params; + + public DHKeyGenerationParameters( + SecureRandom random, + DHParameters params) + { + super(random, getStrength(params)); + + this.params = params; + } + + public DHParameters getParameters() + { + return params; + } + + static int getStrength(DHParameters params) + { + return params.getL() != 0 ? params.getL() : params.getP().bitLength(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/DHKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/DHKeyParameters.java new file mode 100644 index 000000000..6d45b93c5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/DHKeyParameters.java @@ -0,0 +1,54 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + + +public class DHKeyParameters + extends AsymmetricKeyParameter +{ + private DHParameters params; + + protected DHKeyParameters( + boolean isPrivate, + DHParameters params) + { + super(isPrivate); + + this.params = params; + } + + public DHParameters getParameters() + { + return params; + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof DHKeyParameters)) + { + return false; + } + + DHKeyParameters dhKey = (DHKeyParameters)obj; + + if (params == null) + { + return dhKey.getParameters() == null; + } + else + { + return params.equals(dhKey.getParameters()); + } + } + + public int hashCode() + { + int code = isPrivate() ? 0 : 1; + + if (params != null) + { + code ^= params.hashCode(); + } + + return code; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/DHParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/DHParameters.java new file mode 100644 index 000000000..b3ae524d4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/DHParameters.java @@ -0,0 +1,188 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +import java.math.BigInteger; + +public class DHParameters + implements CipherParameters +{ + private static final int DEFAULT_MINIMUM_LENGTH = 160; + + // not final due to compiler bug in "simpler" JDKs + private BigInteger g; + private BigInteger p; + private BigInteger q; + private BigInteger j; + private int m; + private int l; + private DHValidationParameters validation; + + private static int getDefaultMParam( + int lParam) + { + if (lParam == 0) + { + return DEFAULT_MINIMUM_LENGTH; + } + + return lParam < DEFAULT_MINIMUM_LENGTH ? lParam : DEFAULT_MINIMUM_LENGTH; + } + + public DHParameters( + BigInteger p, + BigInteger g) + { + this(p, g, null, 0); + } + + public DHParameters( + BigInteger p, + BigInteger g, + BigInteger q) + { + this(p, g, q, 0); + } + + public DHParameters( + BigInteger p, + BigInteger g, + BigInteger q, + int l) + { + this(p, g, q, getDefaultMParam(l), l, null, null); + } + + public DHParameters( + BigInteger p, + BigInteger g, + BigInteger q, + int m, + int l) + { + this(p, g, q, m, l, null, null); + } + + public DHParameters( + BigInteger p, + BigInteger g, + BigInteger q, + BigInteger j, + DHValidationParameters validation) + { + this(p, g, q, DEFAULT_MINIMUM_LENGTH, 0, j, validation); + } + + public DHParameters( + BigInteger p, + BigInteger g, + BigInteger q, + int m, + int l, + BigInteger j, + DHValidationParameters validation) + { + if (l != 0) + { + if (l >= p.bitLength()) + { + throw new IllegalArgumentException("when l value specified, it must be less than bitlength(p)"); + } + if (l < m) + { + throw new IllegalArgumentException("when l value specified, it may not be less than m value"); + } + } + + this.g = g; + this.p = p; + this.q = q; + this.m = m; + this.l = l; + this.j = j; + this.validation = validation; + } + + public BigInteger getP() + { + return p; + } + + public BigInteger getG() + { + return g; + } + + public BigInteger getQ() + { + return q; + } + + /** + * Return the subgroup factor J. + * + * @return subgroup factor + */ + public BigInteger getJ() + { + return j; + } + + /** + * Return the minimum length of the private value. + * + * @return the minimum length of the private value in bits. + */ + public int getM() + { + return m; + } + + /** + * Return the private value length in bits - if set, zero otherwise + * + * @return the private value length in bits, zero otherwise. + */ + public int getL() + { + return l; + } + + public DHValidationParameters getValidationParameters() + { + return validation; + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof DHParameters)) + { + return false; + } + + DHParameters pm = (DHParameters)obj; + + if (this.getQ() != null) + { + if (!this.getQ().equals(pm.getQ())) + { + return false; + } + } + else + { + if (pm.getQ() != null) + { + return false; + } + } + + return pm.getP().equals(p) && pm.getG().equals(g); + } + + public int hashCode() + { + return getP().hashCode() ^ getG().hashCode() ^ (getQ() != null ? getQ().hashCode() : 0); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/DHPrivateKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/DHPrivateKeyParameters.java new file mode 100644 index 000000000..b9b7052b4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/DHPrivateKeyParameters.java @@ -0,0 +1,41 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class DHPrivateKeyParameters + extends DHKeyParameters +{ + private BigInteger x; + + public DHPrivateKeyParameters( + BigInteger x, + DHParameters params) + { + super(true, params); + + this.x = x; + } + + public BigInteger getX() + { + return x; + } + + public int hashCode() + { + return x.hashCode() ^ super.hashCode(); + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof DHPrivateKeyParameters)) + { + return false; + } + + DHPrivateKeyParameters other = (DHPrivateKeyParameters)obj; + + return other.getX().equals(this.x) && super.equals(obj); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/DHPublicKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/DHPublicKeyParameters.java new file mode 100644 index 000000000..c9a28eda6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/DHPublicKeyParameters.java @@ -0,0 +1,41 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class DHPublicKeyParameters + extends DHKeyParameters +{ + private BigInteger y; + + public DHPublicKeyParameters( + BigInteger y, + DHParameters params) + { + super(false, params); + + this.y = y; + } + + public BigInteger getY() + { + return y; + } + + public int hashCode() + { + return y.hashCode() ^ super.hashCode(); + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof DHPublicKeyParameters)) + { + return false; + } + + DHPublicKeyParameters other = (DHPublicKeyParameters)obj; + + return other.getY().equals(y) && super.equals(obj); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/DHValidationParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/DHValidationParameters.java new file mode 100644 index 000000000..3218ca1c2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/DHValidationParameters.java @@ -0,0 +1,50 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.util.Arrays; + +public class DHValidationParameters +{ + private byte[] seed; + private int counter; + + public DHValidationParameters( + byte[] seed, + int counter) + { + this.seed = seed; + this.counter = counter; + } + + public int getCounter() + { + return counter; + } + + public byte[] getSeed() + { + return seed; + } + + public boolean equals( + Object o) + { + if (!(o instanceof DHValidationParameters)) + { + return false; + } + + DHValidationParameters other = (DHValidationParameters)o; + + if (other.counter != this.counter) + { + return false; + } + + return Arrays.areEqual(this.seed, other.seed); + } + + public int hashCode() + { + return counter ^ Arrays.hashCode(seed); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/DSAKeyGenerationParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/DSAKeyGenerationParameters.java new file mode 100644 index 000000000..3dfd908d4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/DSAKeyGenerationParameters.java @@ -0,0 +1,25 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; + +public class DSAKeyGenerationParameters + extends KeyGenerationParameters +{ + private DSAParameters params; + + public DSAKeyGenerationParameters( + SecureRandom random, + DSAParameters params) + { + super(random, params.getP().bitLength() - 1); + + this.params = params; + } + + public DSAParameters getParameters() + { + return params; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/DSAKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/DSAKeyParameters.java new file mode 100644 index 000000000..382209962 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/DSAKeyParameters.java @@ -0,0 +1,21 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +public class DSAKeyParameters + extends AsymmetricKeyParameter +{ + private DSAParameters params; + + public DSAKeyParameters( + boolean isPrivate, + DSAParameters params) + { + super(isPrivate); + + this.params = params; + } + + public DSAParameters getParameters() + { + return params; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/DSAParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/DSAParameters.java new file mode 100644 index 000000000..82de817bb --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/DSAParameters.java @@ -0,0 +1,74 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +public class DSAParameters + implements CipherParameters +{ + private BigInteger g; + private BigInteger q; + private BigInteger p; + private DSAValidationParameters validation; + + public DSAParameters( + BigInteger p, + BigInteger q, + BigInteger g) + { + this.g = g; + this.p = p; + this.q = q; + } + + public DSAParameters( + BigInteger p, + BigInteger q, + BigInteger g, + DSAValidationParameters params) + { + this.g = g; + this.p = p; + this.q = q; + this.validation = params; + } + + public BigInteger getP() + { + return p; + } + + public BigInteger getQ() + { + return q; + } + + public BigInteger getG() + { + return g; + } + + public DSAValidationParameters getValidationParameters() + { + return validation; + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof DSAParameters)) + { + return false; + } + + DSAParameters pm = (DSAParameters)obj; + + return (pm.getP().equals(p) && pm.getQ().equals(q) && pm.getG().equals(g)); + } + + public int hashCode() + { + return getP().hashCode() ^ getQ().hashCode() ^ getG().hashCode(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/DSAPrivateKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/DSAPrivateKeyParameters.java new file mode 100644 index 000000000..32ab3c890 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/DSAPrivateKeyParameters.java @@ -0,0 +1,23 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class DSAPrivateKeyParameters + extends DSAKeyParameters +{ + private BigInteger x; + + public DSAPrivateKeyParameters( + BigInteger x, + DSAParameters params) + { + super(true, params); + + this.x = x; + } + + public BigInteger getX() + { + return x; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/DSAPublicKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/DSAPublicKeyParameters.java new file mode 100644 index 000000000..08eced7ce --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/DSAPublicKeyParameters.java @@ -0,0 +1,23 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class DSAPublicKeyParameters + extends DSAKeyParameters +{ + private BigInteger y; + + public DSAPublicKeyParameters( + BigInteger y, + DSAParameters params) + { + super(false, params); + + this.y = y; + } + + public BigInteger getY() + { + return y; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/DSAValidationParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/DSAValidationParameters.java new file mode 100644 index 000000000..6bda204bf --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/DSAValidationParameters.java @@ -0,0 +1,50 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.util.Arrays; + +public class DSAValidationParameters +{ + private byte[] seed; + private int counter; + + public DSAValidationParameters( + byte[] seed, + int counter) + { + this.seed = seed; + this.counter = counter; + } + + public int getCounter() + { + return counter; + } + + public byte[] getSeed() + { + return seed; + } + + public int hashCode() + { + return counter ^ Arrays.hashCode(seed); + } + + public boolean equals( + Object o) + { + if (!(o instanceof DSAValidationParameters)) + { + return false; + } + + DSAValidationParameters other = (DSAValidationParameters)o; + + if (other.counter != this.counter) + { + return false; + } + + return Arrays.areEqual(this.seed, other.seed); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/ECDomainParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/ECDomainParameters.java new file mode 100644 index 000000000..8bb473f2d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/ECDomainParameters.java @@ -0,0 +1,81 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.math.ec.ECConstants; +import com.google.bitcoin.bouncycastle.math.ec.ECCurve; +import com.google.bitcoin.bouncycastle.math.ec.ECPoint; + +public class ECDomainParameters + implements ECConstants +{ + ECCurve curve; + byte[] seed; + ECPoint G; + BigInteger n; + BigInteger h; + + public ECDomainParameters( + ECCurve curve, + ECPoint G, + BigInteger n) + { + this.curve = curve; + this.G = G; + this.n = n; + this.h = ONE; + this.seed = null; + } + + public ECDomainParameters( + ECCurve curve, + ECPoint G, + BigInteger n, + BigInteger h) + { + this.curve = curve; + this.G = G; + this.n = n; + this.h = h; + this.seed = null; + } + + public ECDomainParameters( + ECCurve curve, + ECPoint G, + BigInteger n, + BigInteger h, + byte[] seed) + { + this.curve = curve; + this.G = G; + this.n = n; + this.h = h; + this.seed = seed; + } + + public ECCurve getCurve() + { + return curve; + } + + public ECPoint getG() + { + return G; + } + + public BigInteger getN() + { + return n; + } + + public BigInteger getH() + { + return h; + } + + public byte[] getSeed() + { + return seed; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/ECKeyGenerationParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/ECKeyGenerationParameters.java new file mode 100644 index 000000000..e6053b096 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/ECKeyGenerationParameters.java @@ -0,0 +1,25 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; + +public class ECKeyGenerationParameters + extends KeyGenerationParameters +{ + private ECDomainParameters domainParams; + + public ECKeyGenerationParameters( + ECDomainParameters domainParams, + SecureRandom random) + { + super(random, domainParams.getN().bitLength()); + + this.domainParams = domainParams; + } + + public ECDomainParameters getDomainParameters() + { + return domainParams; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/ECKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/ECKeyParameters.java new file mode 100644 index 000000000..95398ec6e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/ECKeyParameters.java @@ -0,0 +1,21 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +public class ECKeyParameters + extends AsymmetricKeyParameter +{ + ECDomainParameters params; + + protected ECKeyParameters( + boolean isPrivate, + ECDomainParameters params) + { + super(isPrivate); + + this.params = params; + } + + public ECDomainParameters getParameters() + { + return params; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/ECPrivateKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/ECPrivateKeyParameters.java new file mode 100644 index 000000000..e313b9b9b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/ECPrivateKeyParameters.java @@ -0,0 +1,22 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class ECPrivateKeyParameters + extends ECKeyParameters +{ + BigInteger d; + + public ECPrivateKeyParameters( + BigInteger d, + ECDomainParameters params) + { + super(true, params); + this.d = d; + } + + public BigInteger getD() + { + return d; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/ECPublicKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/ECPublicKeyParameters.java new file mode 100644 index 000000000..6fd45b869 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/ECPublicKeyParameters.java @@ -0,0 +1,22 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.math.ec.ECPoint; + +public class ECPublicKeyParameters + extends ECKeyParameters +{ + ECPoint Q; + + public ECPublicKeyParameters( + ECPoint Q, + ECDomainParameters params) + { + super(false, params); + this.Q = Q; + } + + public ECPoint getQ() + { + return Q; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalKeyGenerationParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalKeyGenerationParameters.java new file mode 100644 index 000000000..7c4be5501 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalKeyGenerationParameters.java @@ -0,0 +1,30 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; + +public class ElGamalKeyGenerationParameters + extends KeyGenerationParameters +{ + private ElGamalParameters params; + + public ElGamalKeyGenerationParameters( + SecureRandom random, + ElGamalParameters params) + { + super(random, getStrength(params)); + + this.params = params; + } + + public ElGamalParameters getParameters() + { + return params; + } + + static int getStrength(ElGamalParameters params) + { + return params.getL() != 0 ? params.getL() : params.getP().bitLength(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalKeyParameters.java new file mode 100644 index 000000000..35fa44c2b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalKeyParameters.java @@ -0,0 +1,47 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + + +public class ElGamalKeyParameters + extends AsymmetricKeyParameter +{ + private ElGamalParameters params; + + protected ElGamalKeyParameters( + boolean isPrivate, + ElGamalParameters params) + { + super(isPrivate); + + this.params = params; + } + + public ElGamalParameters getParameters() + { + return params; + } + + public int hashCode() + { + return (params != null) ? params.hashCode() : 0; + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof ElGamalKeyParameters)) + { + return false; + } + + ElGamalKeyParameters dhKey = (ElGamalKeyParameters)obj; + + if (params == null) + { + return dhKey.getParameters() == null; + } + else + { + return params.equals(dhKey.getParameters()); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalParameters.java new file mode 100644 index 000000000..ed4838af0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalParameters.java @@ -0,0 +1,69 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +public class ElGamalParameters + implements CipherParameters +{ + private BigInteger g; + private BigInteger p; + private int l; + + public ElGamalParameters( + BigInteger p, + BigInteger g) + { + this(p, g, 0); + } + + public ElGamalParameters( + BigInteger p, + BigInteger g, + int l) + { + this.g = g; + this.p = p; + this.l = l; + } + + public BigInteger getP() + { + return p; + } + + /** + * return the generator - g + */ + public BigInteger getG() + { + return g; + } + + /** + * return private value limit - l + */ + public int getL() + { + return l; + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof ElGamalParameters)) + { + return false; + } + + ElGamalParameters pm = (ElGamalParameters)obj; + + return pm.getP().equals(p) && pm.getG().equals(g) && pm.getL() == l; + } + + public int hashCode() + { + return (getP().hashCode() ^ getG().hashCode()) + l; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalPrivateKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalPrivateKeyParameters.java new file mode 100644 index 000000000..f50386cb2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalPrivateKeyParameters.java @@ -0,0 +1,46 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class ElGamalPrivateKeyParameters + extends ElGamalKeyParameters +{ + private BigInteger x; + + public ElGamalPrivateKeyParameters( + BigInteger x, + ElGamalParameters params) + { + super(true, params); + + this.x = x; + } + + public BigInteger getX() + { + return x; + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof ElGamalPrivateKeyParameters)) + { + return false; + } + + ElGamalPrivateKeyParameters pKey = (ElGamalPrivateKeyParameters)obj; + + if (!pKey.getX().equals(x)) + { + return false; + } + + return super.equals(obj); + } + + public int hashCode() + { + return getX().hashCode(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalPublicKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalPublicKeyParameters.java new file mode 100644 index 000000000..3c058f21d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/ElGamalPublicKeyParameters.java @@ -0,0 +1,41 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class ElGamalPublicKeyParameters + extends ElGamalKeyParameters +{ + private BigInteger y; + + public ElGamalPublicKeyParameters( + BigInteger y, + ElGamalParameters params) + { + super(false, params); + + this.y = y; + } + + public BigInteger getY() + { + return y; + } + + public int hashCode() + { + return y.hashCode() ^ super.hashCode(); + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof ElGamalPublicKeyParameters)) + { + return false; + } + + ElGamalPublicKeyParameters other = (ElGamalPublicKeyParameters)obj; + + return other.getY().equals(y) && super.equals(obj); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410KeyGenerationParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410KeyGenerationParameters.java new file mode 100644 index 000000000..a24c895c7 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410KeyGenerationParameters.java @@ -0,0 +1,25 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; + +import java.security.SecureRandom; + +public class GOST3410KeyGenerationParameters + extends KeyGenerationParameters +{ + private GOST3410Parameters params; + + public GOST3410KeyGenerationParameters( + SecureRandom random, + GOST3410Parameters params) + { + super(random, params.getP().bitLength() - 1); + + this.params = params; + } + + public GOST3410Parameters getParameters() + { + return params; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410KeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410KeyParameters.java new file mode 100644 index 000000000..48daf09d3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410KeyParameters.java @@ -0,0 +1,21 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +public class GOST3410KeyParameters + extends AsymmetricKeyParameter +{ + private GOST3410Parameters params; + + public GOST3410KeyParameters( + boolean isPrivate, + GOST3410Parameters params) + { + super(isPrivate); + + this.params = params; + } + + public GOST3410Parameters getParameters() + { + return params; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410Parameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410Parameters.java new file mode 100644 index 000000000..f3ca59d1b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410Parameters.java @@ -0,0 +1,74 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +import java.math.BigInteger; + +public class GOST3410Parameters + implements CipherParameters +{ + private BigInteger p; + private BigInteger q; + private BigInteger a; + private GOST3410ValidationParameters validation; + + public GOST3410Parameters( + BigInteger p, + BigInteger q, + BigInteger a) + { + this.p = p; + this.q = q; + this.a = a; + } + + public GOST3410Parameters( + BigInteger p, + BigInteger q, + BigInteger a, + GOST3410ValidationParameters params) + { + this.a = a; + this.p = p; + this.q = q; + this.validation = params; + } + + public BigInteger getP() + { + return p; + } + + public BigInteger getQ() + { + return q; + } + + public BigInteger getA() + { + return a; + } + + public GOST3410ValidationParameters getValidationParameters() + { + return validation; + } + + public int hashCode() + { + return p.hashCode() ^ q.hashCode() ^ a.hashCode(); + } + + public boolean equals( + Object obj) + { + if (!(obj instanceof GOST3410Parameters)) + { + return false; + } + + GOST3410Parameters pm = (GOST3410Parameters)obj; + + return (pm.getP().equals(p) && pm.getQ().equals(q) && pm.getA().equals(a)); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410PrivateKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410PrivateKeyParameters.java new file mode 100644 index 000000000..8a809e944 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410PrivateKeyParameters.java @@ -0,0 +1,23 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class GOST3410PrivateKeyParameters + extends GOST3410KeyParameters +{ + private BigInteger x; + + public GOST3410PrivateKeyParameters( + BigInteger x, + GOST3410Parameters params) + { + super(true, params); + + this.x = x; + } + + public BigInteger getX() + { + return x; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410PublicKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410PublicKeyParameters.java new file mode 100644 index 000000000..3fa4720a3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410PublicKeyParameters.java @@ -0,0 +1,23 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class GOST3410PublicKeyParameters + extends GOST3410KeyParameters +{ + private BigInteger y; + + public GOST3410PublicKeyParameters( + BigInteger y, + GOST3410Parameters params) + { + super(false, params); + + this.y = y; + } + + public BigInteger getY() + { + return y; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410ValidationParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410ValidationParameters.java new file mode 100644 index 000000000..5b5aaaf38 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/GOST3410ValidationParameters.java @@ -0,0 +1,84 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +public class GOST3410ValidationParameters +{ + private int x0; + private int c; + private long x0L; + private long cL; + + + public GOST3410ValidationParameters( + int x0, + int c) + { + this.x0 = x0; + this.c = c; + } + + public GOST3410ValidationParameters( + long x0L, + long cL) + { + this.x0L = x0L; + this.cL = cL; + } + + public int getC() + { + return c; + } + + public int getX0() + { + return x0; + } + + public long getCL() + { + return cL; + } + + public long getX0L() + { + return x0L; + } + + public boolean equals( + Object o) + { + if (!(o instanceof GOST3410ValidationParameters)) + { + return false; + } + + GOST3410ValidationParameters other = (GOST3410ValidationParameters)o; + + if (other.c != this.c) + { + return false; + } + + if (other.x0 != this.x0) + { + return false; + } + + if (other.cL != this.cL) + { + return false; + } + + if (other.x0L != this.x0L) + { + return false; + } + + return true; + } + + public int hashCode() + { + return x0 ^ c ^ (int) x0L ^ (int)(x0L >> 32) ^ (int) cL ^ (int)(cL >> 32); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/IESParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/IESParameters.java new file mode 100644 index 000000000..33e73d778 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/IESParameters.java @@ -0,0 +1,44 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +/** + * parameters for using an integrated cipher in stream mode. + */ +public class IESParameters + implements CipherParameters +{ + private byte[] derivation; + private byte[] encoding; + private int macKeySize; + + /** + * @param derivation the derivation parameter for the KDF function. + * @param encoding the encoding parameter for the KDF function. + * @param macKeySize the size of the MAC key (in bits). + */ + public IESParameters( + byte[] derivation, + byte[] encoding, + int macKeySize) + { + this.derivation = derivation; + this.encoding = encoding; + this.macKeySize = macKeySize; + } + + public byte[] getDerivationV() + { + return derivation; + } + + public byte[] getEncodingV() + { + return encoding; + } + + public int getMacKeySize() + { + return macKeySize; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/IESWithCipherParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/IESWithCipherParameters.java new file mode 100644 index 000000000..9706e6482 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/IESWithCipherParameters.java @@ -0,0 +1,30 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + + +public class IESWithCipherParameters + extends IESParameters +{ + private int cipherKeySize; + + /** + * @param derivation the derivation parameter for the KDF function. + * @param encoding the encoding parameter for the KDF function. + * @param macKeySize the size of the MAC key (in bits). + * @param cipherKeySize the size of the associated Cipher key (in bits). + */ + public IESWithCipherParameters( + byte[] derivation, + byte[] encoding, + int macKeySize, + int cipherKeySize) + { + super(derivation, encoding, macKeySize); + + this.cipherKeySize = cipherKeySize; + } + + public int getCipherKeySize() + { + return cipherKeySize; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/ISO18033KDFParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/ISO18033KDFParameters.java new file mode 100644 index 000000000..1fbe1ed21 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/ISO18033KDFParameters.java @@ -0,0 +1,23 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.DerivationParameters; + +/** + * parameters for Key derivation functions for ISO-18033 + */ +public class ISO18033KDFParameters + implements DerivationParameters +{ + byte[] seed; + + public ISO18033KDFParameters( + byte[] seed) + { + this.seed = seed; + } + + public byte[] getSeed() + { + return seed; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/KDFParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/KDFParameters.java new file mode 100644 index 000000000..5d2df2fd2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/KDFParameters.java @@ -0,0 +1,31 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.DerivationParameters; + +/** + * parameters for Key derivation functions for IEEE P1363a + */ +public class KDFParameters + implements DerivationParameters +{ + byte[] iv; + byte[] shared; + + public KDFParameters( + byte[] shared, + byte[] iv) + { + this.shared = shared; + this.iv = iv; + } + + public byte[] getSharedSecret() + { + return shared; + } + + public byte[] getIV() + { + return iv; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/KeyParameter.java b/src/com/google/bitcoin/bouncycastle/crypto/params/KeyParameter.java new file mode 100644 index 000000000..6db0e1e90 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/KeyParameter.java @@ -0,0 +1,30 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +public class KeyParameter + implements CipherParameters +{ + private byte[] key; + + public KeyParameter( + byte[] key) + { + this(key, 0, key.length); + } + + public KeyParameter( + byte[] key, + int keyOff, + int keyLen) + { + this.key = new byte[keyLen]; + + System.arraycopy(key, keyOff, this.key, 0, keyLen); + } + + public byte[] getKey() + { + return key; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/MGFParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/MGFParameters.java new file mode 100644 index 000000000..43c18b1fd --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/MGFParameters.java @@ -0,0 +1,32 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.DerivationParameters; + +/** + * parameters for mask derivation functions. + */ +public class MGFParameters + implements DerivationParameters +{ + byte[] seed; + + public MGFParameters( + byte[] seed) + { + this(seed, 0, seed.length); + } + + public MGFParameters( + byte[] seed, + int off, + int len) + { + this.seed = new byte[len]; + System.arraycopy(seed, off, this.seed, 0, len); + } + + public byte[] getSeed() + { + return seed; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/MQVPrivateParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/MQVPrivateParameters.java new file mode 100644 index 000000000..feb5f67c0 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/MQVPrivateParameters.java @@ -0,0 +1,43 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +public class MQVPrivateParameters + implements CipherParameters +{ + private ECPrivateKeyParameters staticPrivateKey; + private ECPrivateKeyParameters ephemeralPrivateKey; + private ECPublicKeyParameters ephemeralPublicKey; + + public MQVPrivateParameters( + ECPrivateKeyParameters staticPrivateKey, + ECPrivateKeyParameters ephemeralPrivateKey) + { + this(staticPrivateKey, ephemeralPrivateKey, null); + } + + public MQVPrivateParameters( + ECPrivateKeyParameters staticPrivateKey, + ECPrivateKeyParameters ephemeralPrivateKey, + ECPublicKeyParameters ephemeralPublicKey) + { + this.staticPrivateKey = staticPrivateKey; + this.ephemeralPrivateKey = ephemeralPrivateKey; + this.ephemeralPublicKey = ephemeralPublicKey; + } + + public ECPrivateKeyParameters getStaticPrivateKey() + { + return staticPrivateKey; + } + + public ECPrivateKeyParameters getEphemeralPrivateKey() + { + return ephemeralPrivateKey; + } + + public ECPublicKeyParameters getEphemeralPublicKey() + { + return ephemeralPublicKey; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/MQVPublicParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/MQVPublicParameters.java new file mode 100644 index 000000000..e431fde56 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/MQVPublicParameters.java @@ -0,0 +1,28 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +public class MQVPublicParameters + implements CipherParameters +{ + private ECPublicKeyParameters staticPublicKey; + private ECPublicKeyParameters ephemeralPublicKey; + + public MQVPublicParameters( + ECPublicKeyParameters staticPublicKey, + ECPublicKeyParameters ephemeralPublicKey) + { + this.staticPublicKey = staticPublicKey; + this.ephemeralPublicKey = ephemeralPublicKey; + } + + public ECPublicKeyParameters getStaticPublicKey() + { + return staticPublicKey; + } + + public ECPublicKeyParameters getEphemeralPublicKey() + { + return ephemeralPublicKey; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/NaccacheSternKeyGenerationParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/NaccacheSternKeyGenerationParameters.java new file mode 100644 index 000000000..673527094 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/NaccacheSternKeyGenerationParameters.java @@ -0,0 +1,97 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; + +/** + * Parameters for NaccacheStern public private key generation. For details on + * this cipher, please see + * + * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + */ +public class NaccacheSternKeyGenerationParameters extends KeyGenerationParameters +{ + + // private BigInteger publicExponent; + private int certainty; + + private int cntSmallPrimes; + + private boolean debug = false; + + /** + * Parameters for generating a NaccacheStern KeyPair. + * + * @param random + * The source of randomness + * @param strength + * The desired strength of the Key in Bits + * @param certainty + * the probability that the generated primes are not really prime + * as integer: 2^(-certainty) is then the probability + * @param cntSmallPrimes + * How many small key factors are desired + */ + public NaccacheSternKeyGenerationParameters(SecureRandom random, int strength, int certainty, int cntSmallPrimes) + { + this(random, strength, certainty, cntSmallPrimes, false); + } + + /** + * Parameters for a NaccacheStern KeyPair. + * + * @param random + * The source of randomness + * @param strength + * The desired strength of the Key in Bits + * @param certainty + * the probability that the generated primes are not really prime + * as integer: 2^(-certainty) is then the probability + * @param cntSmallPrimes + * How many small key factors are desired + * @param debug + * Turn debugging on or off (reveals secret information, use with + * caution) + */ + public NaccacheSternKeyGenerationParameters(SecureRandom random, + int strength, int certainty, int cntSmallPrimes, boolean debug) + { + super(random, strength); + + this.certainty = certainty; + if (cntSmallPrimes % 2 == 1) + { + throw new IllegalArgumentException("cntSmallPrimes must be a multiple of 2"); + } + if (cntSmallPrimes < 30) + { + throw new IllegalArgumentException("cntSmallPrimes must be >= 30 for security reasons"); + } + this.cntSmallPrimes = cntSmallPrimes; + + this.debug = debug; + } + + /** + * @return Returns the certainty. + */ + public int getCertainty() + { + return certainty; + } + + /** + * @return Returns the cntSmallPrimes. + */ + public int getCntSmallPrimes() + { + return cntSmallPrimes; + } + + public boolean isDebug() + { + return debug; + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/NaccacheSternKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/NaccacheSternKeyParameters.java new file mode 100644 index 000000000..048f4d2f8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/NaccacheSternKeyParameters.java @@ -0,0 +1,53 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; + +/** + * Public key parameters for NaccacheStern cipher. For details on this cipher, + * please see + * + * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + */ +public class NaccacheSternKeyParameters extends AsymmetricKeyParameter +{ + + private BigInteger g, n; + + int lowerSigmaBound; + + /** + * @param privateKey + */ + public NaccacheSternKeyParameters(boolean privateKey, BigInteger g, BigInteger n, int lowerSigmaBound) + { + super(privateKey); + this.g = g; + this.n = n; + this.lowerSigmaBound = lowerSigmaBound; + } + + /** + * @return Returns the g. + */ + public BigInteger getG() + { + return g; + } + + /** + * @return Returns the lowerSigmaBound. + */ + public int getLowerSigmaBound() + { + return lowerSigmaBound; + } + + /** + * @return Returns the n. + */ + public BigInteger getModulus() + { + return n; + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/NaccacheSternPrivateKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/NaccacheSternPrivateKeyParameters.java new file mode 100644 index 000000000..fc5ddadc6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/NaccacheSternPrivateKeyParameters.java @@ -0,0 +1,50 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; +import java.util.Vector; + +/** + * Private key parameters for NaccacheStern cipher. For details on this cipher, + * please see + * + * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + */ +public class NaccacheSternPrivateKeyParameters extends NaccacheSternKeyParameters +{ + private BigInteger phi_n; + private Vector smallPrimes; + + /** + * Constructs a NaccacheSternPrivateKey + * + * @param g + * the public enryption parameter g + * @param n + * the public modulus n = p*q + * @param lowerSigmaBound + * the public lower sigma bound up to which data can be encrypted + * @param smallPrimes + * the small primes, of which sigma is constructed in the right + * order + * @param phi_n + * the private modulus phi(n) = (p-1)(q-1) + */ + public NaccacheSternPrivateKeyParameters(BigInteger g, BigInteger n, + int lowerSigmaBound, Vector smallPrimes, + BigInteger phi_n) + { + super(true, g, n, lowerSigmaBound); + this.smallPrimes = smallPrimes; + this.phi_n = phi_n; + } + + public BigInteger getPhi_n() + { + return phi_n; + } + + public Vector getSmallPrimes() + { + return smallPrimes; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithIV.java b/src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithIV.java new file mode 100644 index 000000000..f29693430 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithIV.java @@ -0,0 +1,39 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +public class ParametersWithIV + implements CipherParameters +{ + private byte[] iv; + private CipherParameters parameters; + + public ParametersWithIV( + CipherParameters parameters, + byte[] iv) + { + this(parameters, iv, 0, iv.length); + } + + public ParametersWithIV( + CipherParameters parameters, + byte[] iv, + int ivOff, + int ivLen) + { + this.iv = new byte[ivLen]; + this.parameters = parameters; + + System.arraycopy(iv, ivOff, this.iv, 0, ivLen); + } + + public byte[] getIV() + { + return iv; + } + + public CipherParameters getParameters() + { + return parameters; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithRandom.java b/src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithRandom.java new file mode 100644 index 000000000..15518f107 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithRandom.java @@ -0,0 +1,36 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +import java.security.SecureRandom; + +public class ParametersWithRandom + implements CipherParameters +{ + private SecureRandom random; + private CipherParameters parameters; + + public ParametersWithRandom( + CipherParameters parameters, + SecureRandom random) + { + this.random = random; + this.parameters = parameters; + } + + public ParametersWithRandom( + CipherParameters parameters) + { + this(parameters, new SecureRandom()); + } + + public SecureRandom getRandom() + { + return random; + } + + public CipherParameters getParameters() + { + return parameters; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithSBox.java b/src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithSBox.java new file mode 100644 index 000000000..e2b16e0e3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithSBox.java @@ -0,0 +1,28 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +public class ParametersWithSBox + implements CipherParameters +{ + private CipherParameters parameters; + private byte[] sBox; + + public ParametersWithSBox( + CipherParameters parameters, + byte[] sBox) + { + this.parameters = parameters; + this.sBox = sBox; + } + + public byte[] getSBox() + { + return sBox; + } + + public CipherParameters getParameters() + { + return parameters; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithSalt.java b/src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithSalt.java new file mode 100644 index 000000000..e01cad560 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/ParametersWithSalt.java @@ -0,0 +1,42 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +/** + * Cipher parameters with a fixed salt value associated with them. + */ +public class ParametersWithSalt + implements CipherParameters +{ + private byte[] salt; + private CipherParameters parameters; + + public ParametersWithSalt( + CipherParameters parameters, + byte[] salt) + { + this(parameters, salt, 0, salt.length); + } + + public ParametersWithSalt( + CipherParameters parameters, + byte[] salt, + int saltOff, + int saltLen) + { + this.salt = new byte[saltLen]; + this.parameters = parameters; + + System.arraycopy(salt, saltOff, this.salt, 0, saltLen); + } + + public byte[] getSalt() + { + return salt; + } + + public CipherParameters getParameters() + { + return parameters; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/RC2Parameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/RC2Parameters.java new file mode 100644 index 000000000..f4813df46 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/RC2Parameters.java @@ -0,0 +1,36 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +public class RC2Parameters + implements CipherParameters +{ + private byte[] key; + private int bits; + + public RC2Parameters( + byte[] key) + { + this(key, (key.length > 128) ? 1024 : (key.length * 8)); + } + + public RC2Parameters( + byte[] key, + int bits) + { + this.key = new byte[key.length]; + this.bits = bits; + + System.arraycopy(key, 0, this.key, 0, key.length); + } + + public byte[] getKey() + { + return key; + } + + public int getEffectiveKeyBits() + { + return bits; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/RC5Parameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/RC5Parameters.java new file mode 100644 index 000000000..6b478d67c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/RC5Parameters.java @@ -0,0 +1,35 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +public class RC5Parameters + implements CipherParameters +{ + private byte[] key; + private int rounds; + + public RC5Parameters( + byte[] key, + int rounds) + { + if (key.length > 255) + { + throw new IllegalArgumentException("RC5 key length can be no greater than 255"); + } + + this.key = new byte[key.length]; + this.rounds = rounds; + + System.arraycopy(key, 0, this.key, 0, key.length); + } + + public byte[] getKey() + { + return key; + } + + public int getRounds() + { + return rounds; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/RSABlindingParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/RSABlindingParameters.java new file mode 100644 index 000000000..da4ced61c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/RSABlindingParameters.java @@ -0,0 +1,35 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; + +import java.math.BigInteger; + +public class RSABlindingParameters + implements CipherParameters +{ + private RSAKeyParameters publicKey; + private BigInteger blindingFactor; + + public RSABlindingParameters( + RSAKeyParameters publicKey, + BigInteger blindingFactor) + { + if (publicKey instanceof RSAPrivateCrtKeyParameters) + { + throw new IllegalArgumentException("RSA parameters should be for a public key"); + } + + this.publicKey = publicKey; + this.blindingFactor = blindingFactor; + } + + public RSAKeyParameters getPublicKey() + { + return publicKey; + } + + public BigInteger getBlindingFactor() + { + return blindingFactor; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/RSAKeyGenerationParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/RSAKeyGenerationParameters.java new file mode 100644 index 000000000..12a775840 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/RSAKeyGenerationParameters.java @@ -0,0 +1,48 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import com.google.bitcoin.bouncycastle.crypto.KeyGenerationParameters; + +public class RSAKeyGenerationParameters + extends KeyGenerationParameters +{ + private BigInteger publicExponent; + private int certainty; + + public RSAKeyGenerationParameters( + BigInteger publicExponent, + SecureRandom random, + int strength, + int certainty) + { + super(random, strength); + + if (strength < 12) + { + throw new IllegalArgumentException("key strength too small"); + } + + // + // public exponent cannot be even + // + if (!publicExponent.testBit(0)) + { + throw new IllegalArgumentException("public exponent cannot be even"); + } + + this.publicExponent = publicExponent; + this.certainty = certainty; + } + + public BigInteger getPublicExponent() + { + return publicExponent; + } + + public int getCertainty() + { + return certainty; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/RSAKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/RSAKeyParameters.java new file mode 100644 index 000000000..61b068a0b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/RSAKeyParameters.java @@ -0,0 +1,31 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class RSAKeyParameters + extends AsymmetricKeyParameter +{ + private BigInteger modulus; + private BigInteger exponent; + + public RSAKeyParameters( + boolean isPrivate, + BigInteger modulus, + BigInteger exponent) + { + super(isPrivate); + + this.modulus = modulus; + this.exponent = exponent; + } + + public BigInteger getModulus() + { + return modulus; + } + + public BigInteger getExponent() + { + return exponent; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/params/RSAPrivateCrtKeyParameters.java b/src/com/google/bitcoin/bouncycastle/crypto/params/RSAPrivateCrtKeyParameters.java new file mode 100644 index 000000000..5436f8632 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/params/RSAPrivateCrtKeyParameters.java @@ -0,0 +1,67 @@ +package com.google.bitcoin.bouncycastle.crypto.params; + +import java.math.BigInteger; + +public class RSAPrivateCrtKeyParameters + extends RSAKeyParameters +{ + private BigInteger e; + private BigInteger p; + private BigInteger q; + private BigInteger dP; + private BigInteger dQ; + private BigInteger qInv; + + /** + * + */ + public RSAPrivateCrtKeyParameters( + BigInteger modulus, + BigInteger publicExponent, + BigInteger privateExponent, + BigInteger p, + BigInteger q, + BigInteger dP, + BigInteger dQ, + BigInteger qInv) + { + super(true, modulus, privateExponent); + + this.e = publicExponent; + this.p = p; + this.q = q; + this.dP = dP; + this.dQ = dQ; + this.qInv = qInv; + } + + public BigInteger getPublicExponent() + { + return e; + } + + public BigInteger getP() + { + return p; + } + + public BigInteger getQ() + { + return q; + } + + public BigInteger getDP() + { + return dP; + } + + public BigInteger getDQ() + { + return dQ; + } + + public BigInteger getQInv() + { + return qInv; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/prng/DigestRandomGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/prng/DigestRandomGenerator.java new file mode 100644 index 000000000..fd00c0e3b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/prng/DigestRandomGenerator.java @@ -0,0 +1,123 @@ +package com.google.bitcoin.bouncycastle.crypto.prng; + +import com.google.bitcoin.bouncycastle.crypto.Digest; + +/** + * Random generation based on the digest with counter. Calling addSeedMaterial will + * always increase the entropy of the hash. + *

    + * Internal access to the digest is synchronized so a single one of these can be shared. + *

    + */ +public class DigestRandomGenerator + implements RandomGenerator +{ + private static long CYCLE_COUNT = 10; + + private long stateCounter; + private long seedCounter; + private Digest digest; + private byte[] state; + private byte[] seed; + + // public constructors + public DigestRandomGenerator( + Digest digest) + { + this.digest = digest; + + this.seed = new byte[digest.getDigestSize()]; + this.seedCounter = 1; + + this.state = new byte[digest.getDigestSize()]; + this.stateCounter = 1; + } + + public void addSeedMaterial(byte[] inSeed) + { + synchronized (this) + { + digestUpdate(inSeed); + digestUpdate(seed); + digestDoFinal(seed); + } + } + + public void addSeedMaterial(long rSeed) + { + synchronized (this) + { + digestAddCounter(rSeed); + digestUpdate(seed); + + digestDoFinal(seed); + } + } + + public void nextBytes(byte[] bytes) + { + nextBytes(bytes, 0, bytes.length); + } + + public void nextBytes(byte[] bytes, int start, int len) + { + synchronized (this) + { + int stateOff = 0; + + generateState(); + + int end = start + len; + for (int i = start; i != end; i++) + { + if (stateOff == state.length) + { + generateState(); + stateOff = 0; + } + bytes[i] = state[stateOff++]; + } + } + } + + private void cycleSeed() + { + digestUpdate(seed); + digestAddCounter(seedCounter++); + + digestDoFinal(seed); + } + + private void generateState() + { + digestAddCounter(stateCounter++); + digestUpdate(state); + digestUpdate(seed); + + digestDoFinal(state); + + if ((stateCounter % CYCLE_COUNT) == 0) + { + cycleSeed(); + } + } + + private void digestAddCounter(long seed) + { + for (int i = 0; i != 8; i++) + { + digest.update((byte)seed); + seed >>>= 8; + } + } + + private void digestUpdate(byte[] inSeed) + { + digest.update(inSeed, 0, inSeed.length); + } + + private void digestDoFinal(byte[] result) + { + digest.doFinal(result, 0); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/prng/RandomGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/prng/RandomGenerator.java new file mode 100644 index 000000000..6b041cb03 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/prng/RandomGenerator.java @@ -0,0 +1,38 @@ +package com.google.bitcoin.bouncycastle.crypto.prng; + +/** + * Generic interface for objects generating random bytes. + */ +public interface RandomGenerator +{ + /** + * Add more seed material to the generator. + * + * @param seed a byte array to be mixed into the generator's state. + */ + void addSeedMaterial(byte[] seed); + + /** + * Add more seed material to the generator. + * + * @param seed a long value to be mixed into the generator's state. + */ + void addSeedMaterial(long seed); + + /** + * Fill bytes with random values. + * + * @param bytes byte array to be filled. + */ + void nextBytes(byte[] bytes); + + /** + * Fill part of bytes with random values. + * + * @param bytes byte array to be filled. + * @param start index to start filling at. + * @param len length of segment to fill. + */ + void nextBytes(byte[] bytes, int start, int len); + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/prng/ReversedWindowGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/prng/ReversedWindowGenerator.java new file mode 100644 index 000000000..44390499a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/prng/ReversedWindowGenerator.java @@ -0,0 +1,111 @@ +package com.google.bitcoin.bouncycastle.crypto.prng; + +/** + * Takes bytes generated by an underling RandomGenerator and reverses the order in + * each small window (of configurable size). + *

    + * Access to internals is synchronized so a single one of these can be shared. + *

    + */ +public class ReversedWindowGenerator + implements RandomGenerator +{ + private final RandomGenerator generator; + + private byte[] window; + private int windowCount; + + public ReversedWindowGenerator( + RandomGenerator generator, + int windowSize) + { + if (generator == null) + { + throw new IllegalArgumentException("generator cannot be null"); + } + if (windowSize < 2) + { + throw new IllegalArgumentException("windowSize must be at least 2"); + } + + this.generator = generator; + this.window = new byte[windowSize]; + } + + /** + * Add more seed material to the generator. + * + * @param seed a byte array to be mixed into the generator's state. + */ + public void addSeedMaterial( + byte[] seed) + { + synchronized (this) + { + windowCount = 0; + generator.addSeedMaterial(seed); + } + } + + /** + * Add more seed material to the generator. + * + * @param seed a long value to be mixed into the generator's state. + */ + public void addSeedMaterial( + long seed) + { + synchronized (this) + { + windowCount = 0; + generator.addSeedMaterial(seed); + } + } + + /** + * Fill bytes with random values. + * + * @param bytes byte array to be filled. + */ + public void nextBytes( + byte[] bytes) + { + doNextBytes(bytes, 0, bytes.length); + } + + /** + * Fill part of bytes with random values. + * + * @param bytes byte array to be filled. + * @param start index to start filling at. + * @param len length of segment to fill. + */ + public void nextBytes( + byte[] bytes, + int start, + int len) + { + doNextBytes(bytes, start, len); + } + + private void doNextBytes( + byte[] bytes, + int start, + int len) + { + synchronized (this) + { + int done = 0; + while (done < len) + { + if (windowCount < 1) + { + generator.nextBytes(window, 0, window.length); + windowCount = window.length; + } + + bytes[start + done++] = window[--windowCount]; + } + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/prng/ThreadedSeedGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/prng/ThreadedSeedGenerator.java new file mode 100644 index 000000000..42049d7d5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/prng/ThreadedSeedGenerator.java @@ -0,0 +1,95 @@ +package com.google.bitcoin.bouncycastle.crypto.prng; + +/** + * A thread based seed generator - one source of randomness. + *

    + * Based on an idea from Marcus Lippert. + *

    + */ +public class ThreadedSeedGenerator +{ + private class SeedGenerator + implements Runnable + { + private volatile int counter = 0; + private volatile boolean stop = false; + + public void run() + { + while (!this.stop) + { + this.counter++; + } + + } + + public byte[] generateSeed( + int numbytes, + boolean fast) + { + Thread t = new Thread(this); + byte[] result = new byte[numbytes]; + this.counter = 0; + this.stop = false; + int last = 0; + int end; + + t.start(); + if(fast) + { + end = numbytes; + } + else + { + end = numbytes * 8; + } + for (int i = 0; i < end; i++) + { + while (this.counter == last) + { + try + { + Thread.sleep(1); + } + catch (InterruptedException e) + { + // ignore + } + } + last = this.counter; + if (fast) + { + result[i] = (byte) (last & 0xff); + } + else + { + int bytepos = i/8; + result[bytepos] = (byte) ((result[bytepos] << 1) | (last & 1)); + } + + } + stop = true; + return result; + } + } + + /** + * Generate seed bytes. Set fast to false for best quality. + *

    + * If fast is set to true, the code should be round about 8 times faster when + * generating a long sequence of random bytes. 20 bytes of random values using + * the fast mode take less than half a second on a Nokia e70. If fast is set to false, + * it takes round about 2500 ms. + *

    + * @param numBytes the number of bytes to generate + * @param fast true if fast mode should be used + */ + public byte[] generateSeed( + int numBytes, + boolean fast) + { + SeedGenerator gen = new SeedGenerator(); + + return gen.generateSeed(numBytes, fast); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/prng/VMPCRandomGenerator.java b/src/com/google/bitcoin/bouncycastle/crypto/prng/VMPCRandomGenerator.java new file mode 100644 index 000000000..14babfcbd --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/prng/VMPCRandomGenerator.java @@ -0,0 +1,131 @@ +package com.google.bitcoin.bouncycastle.crypto.prng; + +public class VMPCRandomGenerator implements RandomGenerator +{ + private byte n = 0; + + /** + * Permutation generated by code: + * // First 1850 fractional digit of Pi number. + * byte[] key = new BigInteger("14159265358979323846...5068006422512520511").toByteArray(); + * s = 0; + * P = new byte[256]; + * for (int i = 0; i < 256; i++) { + * P[i] = (byte) i; + * } + * for (int m = 0; m < 768; m++) { + * s = P[(s + P[m & 0xff] + key[m % key.length]) & 0xff]; + * byte temp = P[m & 0xff]; + * P[m & 0xff] = P[s & 0xff]; + * P[s & 0xff] = temp; + * } + */ + private byte[] P = + { + (byte) 0xbb, (byte) 0x2c, (byte) 0x62, (byte) 0x7f, + (byte) 0xb5, (byte) 0xaa, (byte) 0xd4, (byte) 0x0d, (byte) 0x81, + (byte) 0xfe, (byte) 0xb2, (byte) 0x82, (byte) 0xcb, (byte) 0xa0, + (byte) 0xa1, (byte) 0x08, (byte) 0x18, (byte) 0x71, (byte) 0x56, + (byte) 0xe8, (byte) 0x49, (byte) 0x02, (byte) 0x10, (byte) 0xc4, + (byte) 0xde, (byte) 0x35, (byte) 0xa5, (byte) 0xec, (byte) 0x80, + (byte) 0x12, (byte) 0xb8, (byte) 0x69, (byte) 0xda, (byte) 0x2f, + (byte) 0x75, (byte) 0xcc, (byte) 0xa2, (byte) 0x09, (byte) 0x36, + (byte) 0x03, (byte) 0x61, (byte) 0x2d, (byte) 0xfd, (byte) 0xe0, + (byte) 0xdd, (byte) 0x05, (byte) 0x43, (byte) 0x90, (byte) 0xad, + (byte) 0xc8, (byte) 0xe1, (byte) 0xaf, (byte) 0x57, (byte) 0x9b, + (byte) 0x4c, (byte) 0xd8, (byte) 0x51, (byte) 0xae, (byte) 0x50, + (byte) 0x85, (byte) 0x3c, (byte) 0x0a, (byte) 0xe4, (byte) 0xf3, + (byte) 0x9c, (byte) 0x26, (byte) 0x23, (byte) 0x53, (byte) 0xc9, + (byte) 0x83, (byte) 0x97, (byte) 0x46, (byte) 0xb1, (byte) 0x99, + (byte) 0x64, (byte) 0x31, (byte) 0x77, (byte) 0xd5, (byte) 0x1d, + (byte) 0xd6, (byte) 0x78, (byte) 0xbd, (byte) 0x5e, (byte) 0xb0, + (byte) 0x8a, (byte) 0x22, (byte) 0x38, (byte) 0xf8, (byte) 0x68, + (byte) 0x2b, (byte) 0x2a, (byte) 0xc5, (byte) 0xd3, (byte) 0xf7, + (byte) 0xbc, (byte) 0x6f, (byte) 0xdf, (byte) 0x04, (byte) 0xe5, + (byte) 0x95, (byte) 0x3e, (byte) 0x25, (byte) 0x86, (byte) 0xa6, + (byte) 0x0b, (byte) 0x8f, (byte) 0xf1, (byte) 0x24, (byte) 0x0e, + (byte) 0xd7, (byte) 0x40, (byte) 0xb3, (byte) 0xcf, (byte) 0x7e, + (byte) 0x06, (byte) 0x15, (byte) 0x9a, (byte) 0x4d, (byte) 0x1c, + (byte) 0xa3, (byte) 0xdb, (byte) 0x32, (byte) 0x92, (byte) 0x58, + (byte) 0x11, (byte) 0x27, (byte) 0xf4, (byte) 0x59, (byte) 0xd0, + (byte) 0x4e, (byte) 0x6a, (byte) 0x17, (byte) 0x5b, (byte) 0xac, + (byte) 0xff, (byte) 0x07, (byte) 0xc0, (byte) 0x65, (byte) 0x79, + (byte) 0xfc, (byte) 0xc7, (byte) 0xcd, (byte) 0x76, (byte) 0x42, + (byte) 0x5d, (byte) 0xe7, (byte) 0x3a, (byte) 0x34, (byte) 0x7a, + (byte) 0x30, (byte) 0x28, (byte) 0x0f, (byte) 0x73, (byte) 0x01, + (byte) 0xf9, (byte) 0xd1, (byte) 0xd2, (byte) 0x19, (byte) 0xe9, + (byte) 0x91, (byte) 0xb9, (byte) 0x5a, (byte) 0xed, (byte) 0x41, + (byte) 0x6d, (byte) 0xb4, (byte) 0xc3, (byte) 0x9e, (byte) 0xbf, + (byte) 0x63, (byte) 0xfa, (byte) 0x1f, (byte) 0x33, (byte) 0x60, + (byte) 0x47, (byte) 0x89, (byte) 0xf0, (byte) 0x96, (byte) 0x1a, + (byte) 0x5f, (byte) 0x93, (byte) 0x3d, (byte) 0x37, (byte) 0x4b, + (byte) 0xd9, (byte) 0xa8, (byte) 0xc1, (byte) 0x1b, (byte) 0xf6, + (byte) 0x39, (byte) 0x8b, (byte) 0xb7, (byte) 0x0c, (byte) 0x20, + (byte) 0xce, (byte) 0x88, (byte) 0x6e, (byte) 0xb6, (byte) 0x74, + (byte) 0x8e, (byte) 0x8d, (byte) 0x16, (byte) 0x29, (byte) 0xf2, + (byte) 0x87, (byte) 0xf5, (byte) 0xeb, (byte) 0x70, (byte) 0xe3, + (byte) 0xfb, (byte) 0x55, (byte) 0x9f, (byte) 0xc6, (byte) 0x44, + (byte) 0x4a, (byte) 0x45, (byte) 0x7d, (byte) 0xe2, (byte) 0x6b, + (byte) 0x5c, (byte) 0x6c, (byte) 0x66, (byte) 0xa9, (byte) 0x8c, + (byte) 0xee, (byte) 0x84, (byte) 0x13, (byte) 0xa7, (byte) 0x1e, + (byte) 0x9d, (byte) 0xdc, (byte) 0x67, (byte) 0x48, (byte) 0xba, + (byte) 0x2e, (byte) 0xe6, (byte) 0xa4, (byte) 0xab, (byte) 0x7c, + (byte) 0x94, (byte) 0x00, (byte) 0x21, (byte) 0xef, (byte) 0xea, + (byte) 0xbe, (byte) 0xca, (byte) 0x72, (byte) 0x4f, (byte) 0x52, + (byte) 0x98, (byte) 0x3f, (byte) 0xc2, (byte) 0x14, (byte) 0x7b, + (byte) 0x3b, (byte) 0x54 }; + + /** + * Value generated in the same way as {@link VMPCRandomGenerator#P}; + */ + private byte s = (byte) 0xbe; + + public VMPCRandomGenerator() + { + } + + public void addSeedMaterial(byte[] seed) + { + for (int m = 0; m < seed.length; m++) + { + s = P[(s + P[n & 0xff] + seed[m]) & 0xff]; + byte temp = P[n & 0xff]; + P[n & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + n = (byte) ((n + 1) & 0xff); + } + } + + public void addSeedMaterial(long seed) + { + byte[] s = new byte[4]; + s[3] = (byte) (seed & 0x000000ff); + s[2] = (byte) ((seed & 0x0000ff00) >> 8); + s[1] = (byte) ((seed & 0x00ff0000) >> 16); + s[0] = (byte) ((seed & 0xff000000) >> 24); + addSeedMaterial(s); + } + + public void nextBytes(byte[] bytes) + { + nextBytes(bytes, 0, bytes.length); + } + + public void nextBytes(byte[] bytes, int start, int len) + { + synchronized (P) + { + int end = start + len; + for (int i = start; i != end; i++) + { + s = P[(s + P[n & 0xff]) & 0xff]; + bytes[i] = P[(P[(P[s & 0xff]) & 0xff] + 1) & 0xff]; + byte temp = P[n & 0xff]; + P[n & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + n = (byte) ((n + 1) & 0xff); + } + } + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/signers/DSADigestSigner.java b/src/com/google/bitcoin/bouncycastle/crypto/signers/DSADigestSigner.java new file mode 100644 index 000000000..672eeb0f5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/signers/DSADigestSigner.java @@ -0,0 +1,154 @@ +package com.google.bitcoin.bouncycastle.crypto.signers; + +import java.io.IOException; +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.asn1.ASN1EncodableVector; +import com.google.bitcoin.bouncycastle.asn1.ASN1Object; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERSequence; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DSA; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.Signer; +import com.google.bitcoin.bouncycastle.crypto.params.AsymmetricKeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; + +public class DSADigestSigner + implements Signer +{ + private final Digest digest; + private final DSA dsaSigner; + private boolean forSigning; + + public DSADigestSigner( + DSA signer, + Digest digest) + { + this.digest = digest; + this.dsaSigner = signer; + } + + public void init( + boolean forSigning, + CipherParameters parameters) + { + this.forSigning = forSigning; + + AsymmetricKeyParameter k; + + if (parameters instanceof ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).getParameters(); + } + else + { + k = (AsymmetricKeyParameter)parameters; + } + + if (forSigning && !k.isPrivate()) + { + throw new IllegalArgumentException("Signing Requires Private Key."); + } + + if (!forSigning && k.isPrivate()) + { + throw new IllegalArgumentException("Verification Requires Public Key."); + } + + reset(); + + dsaSigner.init(forSigning, parameters); + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte input) + { + digest.update(input); + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] input, + int inOff, + int length) + { + digest.update(input, inOff, length); + } + + /** + * Generate a signature for the message we've been loaded with using + * the key we were initialised with. + */ + public byte[] generateSignature() + { + if (!forSigning) + { + throw new IllegalStateException("DSADigestSigner not initialised for signature generation."); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + BigInteger[] sig = dsaSigner.generateSignature(hash); + + return derEncode(sig[0], sig[1]); + } + + public boolean verifySignature( + byte[] signature) + { + if (forSigning) + { + throw new IllegalStateException("DSADigestSigner not initialised for verification"); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + try + { + BigInteger[] sig = derDecode(signature); + return dsaSigner.verifySignature(hash, sig[0], sig[1]); + } + catch (IOException e) + { + return false; + } + } + + public void reset() + { + digest.reset(); + } + + private byte[] derEncode( + BigInteger r, + BigInteger s) + { + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(new DERInteger(r)); + v.add(new DERInteger(s)); + + return new DERSequence(v).getDEREncoded(); + } + + private BigInteger[] derDecode( + byte[] encoding) + throws IOException + { + ASN1Sequence s = (ASN1Sequence)ASN1Object.fromByteArray(encoding); + + return new BigInteger[] + { + ((DERInteger)s.getObjectAt(0)).getValue(), + ((DERInteger)s.getObjectAt(1)).getValue() + }; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/signers/DSASigner.java b/src/com/google/bitcoin/bouncycastle/crypto/signers/DSASigner.java new file mode 100644 index 000000000..93c0a34e4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/signers/DSASigner.java @@ -0,0 +1,138 @@ +package com.google.bitcoin.bouncycastle.crypto.signers; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DSA; +import com.google.bitcoin.bouncycastle.crypto.params.DSAKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DSAParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DSAPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DSAPublicKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * The Digital Signature Algorithm - as described in "Handbook of Applied + * Cryptography", pages 452 - 453. + */ +public class DSASigner + implements DSA +{ + DSAKeyParameters key; + + SecureRandom random; + + public void init( + boolean forSigning, + CipherParameters param) + { + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (DSAPrivateKeyParameters)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.key = (DSAPrivateKeyParameters)param; + } + } + else + { + this.key = (DSAPublicKeyParameters)param; + } + } + + /** + * generate a signature for the given message using the key we were + * initialised with. For conventional DSA the message should be a SHA-1 + * hash of the message of interest. + * + * @param message the message that will be verified later. + */ + public BigInteger[] generateSignature( + byte[] message) + { + DSAParameters params = key.getParameters(); + BigInteger m = calculateE(params.getQ(), message); + BigInteger k; + int qBitLength = params.getQ().bitLength(); + + do + { + k = new BigInteger(qBitLength, random); + } + while (k.compareTo(params.getQ()) >= 0); + + BigInteger r = params.getG().modPow(k, params.getP()).mod(params.getQ()); + + k = k.modInverse(params.getQ()).multiply( + m.add(((DSAPrivateKeyParameters)key).getX().multiply(r))); + + BigInteger s = k.mod(params.getQ()); + + BigInteger[] res = new BigInteger[2]; + + res[0] = r; + res[1] = s; + + return res; + } + + /** + * return true if the value r and s represent a DSA signature for + * the passed in message for standard DSA the message should be a + * SHA-1 hash of the real message to be verified. + */ + public boolean verifySignature( + byte[] message, + BigInteger r, + BigInteger s) + { + DSAParameters params = key.getParameters(); + BigInteger m = calculateE(params.getQ(), message); + BigInteger zero = BigInteger.valueOf(0); + + if (zero.compareTo(r) >= 0 || params.getQ().compareTo(r) <= 0) + { + return false; + } + + if (zero.compareTo(s) >= 0 || params.getQ().compareTo(s) <= 0) + { + return false; + } + + BigInteger w = s.modInverse(params.getQ()); + + BigInteger u1 = m.multiply(w).mod(params.getQ()); + BigInteger u2 = r.multiply(w).mod(params.getQ()); + + u1 = params.getG().modPow(u1, params.getP()); + u2 = ((DSAPublicKeyParameters)key).getY().modPow(u2, params.getP()); + + BigInteger v = u1.multiply(u2).mod(params.getP()).mod(params.getQ()); + + return v.equals(r); + } + + private BigInteger calculateE(BigInteger n, byte[] message) + { + if (n.bitLength() >= message.length * 8) + { + return new BigInteger(1, message); + } + else + { + byte[] trunc = new byte[n.bitLength() / 8]; + + System.arraycopy(message, 0, trunc, 0, trunc.length); + + return new BigInteger(1, trunc); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/signers/ECDSASigner.java b/src/com/google/bitcoin/bouncycastle/crypto/signers/ECDSASigner.java new file mode 100644 index 000000000..782bdbeb8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/signers/ECDSASigner.java @@ -0,0 +1,164 @@ +package com.google.bitcoin.bouncycastle.crypto.signers; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DSA; +import com.google.bitcoin.bouncycastle.crypto.params.ECKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPublicKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.math.ec.ECAlgorithms; +import com.google.bitcoin.bouncycastle.math.ec.ECConstants; +import com.google.bitcoin.bouncycastle.math.ec.ECPoint; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * EC-DSA as described in X9.62 + */ +public class ECDSASigner + implements ECConstants, DSA +{ + ECKeyParameters key; + + SecureRandom random; + + public void init( + boolean forSigning, + CipherParameters param) + { + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (ECPrivateKeyParameters)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.key = (ECPrivateKeyParameters)param; + } + } + else + { + this.key = (ECPublicKeyParameters)param; + } + } + + // 5.3 pg 28 + /** + * generate a signature for the given message using the key we were + * initialised with. For conventional DSA the message should be a SHA-1 + * hash of the message of interest. + * + * @param message the message that will be verified later. + */ + public BigInteger[] generateSignature( + byte[] message) + { + BigInteger n = key.getParameters().getN(); + BigInteger e = calculateE(n, message); + BigInteger r = null; + BigInteger s = null; + + // 5.3.2 + do // generate s + { + BigInteger k = null; + int nBitLength = n.bitLength(); + + do // generate r + { + do + { + k = new BigInteger(nBitLength, random); + } + while (k.equals(ZERO)); + + ECPoint p = key.getParameters().getG().multiply(k); + + // 5.3.3 + BigInteger x = p.getX().toBigInteger(); + + r = x.mod(n); + } + while (r.equals(ZERO)); + + BigInteger d = ((ECPrivateKeyParameters)key).getD(); + + s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n); + } + while (s.equals(ZERO)); + + BigInteger[] res = new BigInteger[2]; + + res[0] = r; + res[1] = s; + + return res; + } + + // 5.4 pg 29 + /** + * return true if the value r and s represent a DSA signature for + * the passed in message (for standard DSA the message should be + * a SHA-1 hash of the real message to be verified). + */ + public boolean verifySignature( + byte[] message, + BigInteger r, + BigInteger s) + { + BigInteger n = key.getParameters().getN(); + BigInteger e = calculateE(n, message); + + // r in the range [1,n-1] + if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) + { + return false; + } + + // s in the range [1,n-1] + if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) + { + return false; + } + + BigInteger c = s.modInverse(n); + + BigInteger u1 = e.multiply(c).mod(n); + BigInteger u2 = r.multiply(c).mod(n); + + ECPoint G = key.getParameters().getG(); + ECPoint Q = ((ECPublicKeyParameters)key).getQ(); + + ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, u1, Q, u2); + + BigInteger v = point.getX().toBigInteger().mod(n); + + return v.equals(r); + } + + private BigInteger calculateE(BigInteger n, byte[] message) + { + if (n.bitLength() > message.length * 8) + { + return new BigInteger(1, message); + } + else + { + int messageBitLength = message.length * 8; + BigInteger trunc = new BigInteger(1, message); + + if (messageBitLength - n.bitLength() > 0) + { + trunc = trunc.shiftRight(messageBitLength - n.bitLength()); + } + + return trunc; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/signers/ECGOST3410Signer.java b/src/com/google/bitcoin/bouncycastle/crypto/signers/ECGOST3410Signer.java new file mode 100644 index 000000000..11136b3f5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/signers/ECGOST3410Signer.java @@ -0,0 +1,152 @@ +package com.google.bitcoin.bouncycastle.crypto.signers; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DSA; +import com.google.bitcoin.bouncycastle.crypto.params.ECKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPublicKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.math.ec.ECAlgorithms; +import com.google.bitcoin.bouncycastle.math.ec.ECConstants; +import com.google.bitcoin.bouncycastle.math.ec.ECPoint; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * GOST R 34.10-2001 Signature Algorithm + */ +public class ECGOST3410Signer + implements DSA +{ + ECKeyParameters key; + + SecureRandom random; + + public void init( + boolean forSigning, + CipherParameters param) + { + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (ECPrivateKeyParameters)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.key = (ECPrivateKeyParameters)param; + } + } + else + { + this.key = (ECPublicKeyParameters)param; + } + } + + /** + * generate a signature for the given message using the key we were + * initialised with. For conventional GOST3410 the message should be a GOST3411 + * hash of the message of interest. + * + * @param message the message that will be verified later. + */ + public BigInteger[] generateSignature( + byte[] message) + { + byte[] mRev = new byte[message.length]; // conversion is little-endian + for (int i = 0; i != mRev.length; i++) + { + mRev[i] = message[mRev.length - 1 - i]; + } + + BigInteger e = new BigInteger(1, mRev); + BigInteger n = key.getParameters().getN(); + + BigInteger r = null; + BigInteger s = null; + + do // generate s + { + BigInteger k = null; + + do // generate r + { + do + { + k = new BigInteger(n.bitLength(), random); + } + while (k.equals(ECConstants.ZERO)); + + ECPoint p = key.getParameters().getG().multiply(k); + + BigInteger x = p.getX().toBigInteger(); + + r = x.mod(n); + } + while (r.equals(ECConstants.ZERO)); + + BigInteger d = ((ECPrivateKeyParameters)key).getD(); + + s = (k.multiply(e)).add(d.multiply(r)).mod(n); + } + while (s.equals(ECConstants.ZERO)); + + BigInteger[] res = new BigInteger[2]; + + res[0] = r; + res[1] = s; + + return res; + } + + /** + * return true if the value r and s represent a GOST3410 signature for + * the passed in message (for standard GOST3410 the message should be + * a GOST3411 hash of the real message to be verified). + */ + public boolean verifySignature( + byte[] message, + BigInteger r, + BigInteger s) + { + byte[] mRev = new byte[message.length]; // conversion is little-endian + for (int i = 0; i != mRev.length; i++) + { + mRev[i] = message[mRev.length - 1 - i]; + } + + BigInteger e = new BigInteger(1, mRev); + BigInteger n = key.getParameters().getN(); + + // r in the range [1,n-1] + if (r.compareTo(ECConstants.ONE) < 0 || r.compareTo(n) >= 0) + { + return false; + } + + // s in the range [1,n-1] + if (s.compareTo(ECConstants.ONE) < 0 || s.compareTo(n) >= 0) + { + return false; + } + + BigInteger v = e.modInverse(n); + + BigInteger z1 = s.multiply(v).mod(n); + BigInteger z2 = (n.subtract(r)).multiply(v).mod(n); + + ECPoint G = key.getParameters().getG(); // P + ECPoint Q = ((ECPublicKeyParameters)key).getQ(); + + ECPoint point = ECAlgorithms.sumOfTwoMultiplies(G, z1, Q, z2); + + BigInteger R = point.getX().toBigInteger().mod(n); + + return R.equals(r); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/signers/ECNRSigner.java b/src/com/google/bitcoin/bouncycastle/crypto/signers/ECNRSigner.java new file mode 100644 index 000000000..79e32ec91 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/signers/ECNRSigner.java @@ -0,0 +1,182 @@ +package com.google.bitcoin.bouncycastle.crypto.signers; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPair; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DSA; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.generators.ECKeyPairGenerator; +import com.google.bitcoin.bouncycastle.crypto.params.ECKeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPublicKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.math.ec.ECAlgorithms; +import com.google.bitcoin.bouncycastle.math.ec.ECConstants; +import com.google.bitcoin.bouncycastle.math.ec.ECPoint; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * EC-NR as described in IEEE 1363-2000 + */ +public class ECNRSigner + implements DSA +{ + private boolean forSigning; + private ECKeyParameters key; + private SecureRandom random; + + public void init( + boolean forSigning, + CipherParameters param) + { + this.forSigning = forSigning; + + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (ECPrivateKeyParameters)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.key = (ECPrivateKeyParameters)param; + } + } + else + { + this.key = (ECPublicKeyParameters)param; + } + } + + // Section 7.2.5 ECSP-NR, pg 34 + /** + * generate a signature for the given message using the key we were + * initialised with. Generally, the order of the curve should be at + * least as long as the hash of the message of interest, and with + * ECNR it *must* be at least as long. + * + * @param digest the digest to be signed. + * @exception DataLengthException if the digest is longer than the key allows + */ + public BigInteger[] generateSignature( + byte[] digest) + { + if (! this.forSigning) + { + throw new IllegalStateException("not initialised for signing"); + } + + BigInteger n = ((ECPrivateKeyParameters)this.key).getParameters().getN(); + int nBitLength = n.bitLength(); + + BigInteger e = new BigInteger(1, digest); + int eBitLength = e.bitLength(); + + ECPrivateKeyParameters privKey = (ECPrivateKeyParameters)key; + + if (eBitLength > nBitLength) + { + throw new DataLengthException("input too large for ECNR key."); + } + + BigInteger r = null; + BigInteger s = null; + + AsymmetricCipherKeyPair tempPair; + do // generate r + { + // generate another, but very temporary, key pair using + // the same EC parameters + ECKeyPairGenerator keyGen = new ECKeyPairGenerator(); + + keyGen.init(new ECKeyGenerationParameters(privKey.getParameters(), this.random)); + + tempPair = keyGen.generateKeyPair(); + + // BigInteger Vx = tempPair.getPublic().getW().getAffineX(); + ECPublicKeyParameters V = (ECPublicKeyParameters)tempPair.getPublic(); // get temp's public key + BigInteger Vx = V.getQ().getX().toBigInteger(); // get the point's x coordinate + + r = Vx.add(e).mod(n); + } + while (r.equals(ECConstants.ZERO)); + + // generate s + BigInteger x = privKey.getD(); // private key value + BigInteger u = ((ECPrivateKeyParameters)tempPair.getPrivate()).getD(); // temp's private key value + s = u.subtract(r.multiply(x)).mod(n); + + BigInteger[] res = new BigInteger[2]; + res[0] = r; + res[1] = s; + + return res; + } + + // Section 7.2.6 ECVP-NR, pg 35 + /** + * return true if the value r and s represent a signature for the + * message passed in. Generally, the order of the curve should be at + * least as long as the hash of the message of interest, and with + * ECNR, it *must* be at least as long. But just in case the signer + * applied mod(n) to the longer digest, this implementation will + * apply mod(n) during verification. + * + * @param digest the digest to be verified. + * @param r the r value of the signature. + * @param s the s value of the signature. + * @exception DataLengthException if the digest is longer than the key allows + */ + public boolean verifySignature( + byte[] digest, + BigInteger r, + BigInteger s) + { + if (this.forSigning) + { + throw new IllegalStateException("not initialised for verifying"); + } + + ECPublicKeyParameters pubKey = (ECPublicKeyParameters)key; + BigInteger n = pubKey.getParameters().getN(); + int nBitLength = n.bitLength(); + + BigInteger e = new BigInteger(1, digest); + int eBitLength = e.bitLength(); + + if (eBitLength > nBitLength) + { + throw new DataLengthException("input too large for ECNR key."); + } + + // r in the range [1,n-1] + if (r.compareTo(ECConstants.ONE) < 0 || r.compareTo(n) >= 0) + { + return false; + } + + // s in the range [0,n-1] NB: ECNR spec says 0 + if (s.compareTo(ECConstants.ZERO) < 0 || s.compareTo(n) >= 0) + { + return false; + } + + // compute P = sG + rW + + ECPoint G = pubKey.getParameters().getG(); + ECPoint W = pubKey.getQ(); + // calculate P using Bouncy math + ECPoint P = ECAlgorithms.sumOfTwoMultiplies(G, s, W, r); + + BigInteger x = P.getX().toBigInteger(); + BigInteger t = r.subtract(x).mod(n); + + return t.equals(e); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/signers/GOST3410Signer.java b/src/com/google/bitcoin/bouncycastle/crypto/signers/GOST3410Signer.java new file mode 100644 index 000000000..c82d50c0f --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/signers/GOST3410Signer.java @@ -0,0 +1,127 @@ +package com.google.bitcoin.bouncycastle.crypto.signers; + +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.DSA; +import com.google.bitcoin.bouncycastle.crypto.params.*; + +import java.security.SecureRandom; +import java.math.BigInteger; + +/** + * GOST R 34.10-94 Signature Algorithm + */ +public class GOST3410Signer + implements DSA +{ + GOST3410KeyParameters key; + + SecureRandom random; + + public void init( + boolean forSigning, + CipherParameters param) + { + if (forSigning) + { + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + this.random = rParam.getRandom(); + this.key = (GOST3410PrivateKeyParameters)rParam.getParameters(); + } + else + { + this.random = new SecureRandom(); + this.key = (GOST3410PrivateKeyParameters)param; + } + } + else + { + this.key = (GOST3410PublicKeyParameters)param; + } + } + + /** + * generate a signature for the given message using the key we were + * initialised with. For conventional GOST3410 the message should be a GOST3411 + * hash of the message of interest. + * + * @param message the message that will be verified later. + */ + public BigInteger[] generateSignature( + byte[] message) + { + byte[] mRev = new byte[message.length]; // conversion is little-endian + for (int i = 0; i != mRev.length; i++) + { + mRev[i] = message[mRev.length - 1 - i]; + } + + BigInteger m = new BigInteger(1, mRev); + GOST3410Parameters params = key.getParameters(); + BigInteger k; + + do + { + k = new BigInteger(params.getQ().bitLength(), random); + } + while (k.compareTo(params.getQ()) >= 0); + + BigInteger r = params.getA().modPow(k, params.getP()).mod(params.getQ()); + + BigInteger s = k.multiply(m). + add(((GOST3410PrivateKeyParameters)key).getX().multiply(r)). + mod(params.getQ()); + + BigInteger[] res = new BigInteger[2]; + + res[0] = r; + res[1] = s; + + return res; + } + + /** + * return true if the value r and s represent a GOST3410 signature for + * the passed in message for standard GOST3410 the message should be a + * GOST3411 hash of the real message to be verified. + */ + public boolean verifySignature( + byte[] message, + BigInteger r, + BigInteger s) + { + byte[] mRev = new byte[message.length]; // conversion is little-endian + for (int i = 0; i != mRev.length; i++) + { + mRev[i] = message[mRev.length - 1 - i]; + } + + BigInteger m = new BigInteger(1, mRev); + GOST3410Parameters params = key.getParameters(); + BigInteger zero = BigInteger.valueOf(0); + + if (zero.compareTo(r) >= 0 || params.getQ().compareTo(r) <= 0) + { + return false; + } + + if (zero.compareTo(s) >= 0 || params.getQ().compareTo(s) <= 0) + { + return false; + } + + BigInteger v = m.modPow(params.getQ().subtract(new BigInteger("2")),params.getQ()); + + BigInteger z1 = s.multiply(v).mod(params.getQ()); + BigInteger z2 = (params.getQ().subtract(r)).multiply(v).mod(params.getQ()); + + z1 = params.getA().modPow(z1, params.getP()); + z2 = ((GOST3410PublicKeyParameters)key).getY().modPow(z2, params.getP()); + + BigInteger u = z1.multiply(z2).mod(params.getP()).mod(params.getQ()); + + return u.equals(r); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/signers/GenericSigner.java b/src/com/google/bitcoin/bouncycastle/crypto/signers/GenericSigner.java new file mode 100644 index 000000000..fbcdf0f52 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/signers/GenericSigner.java @@ -0,0 +1,136 @@ +package com.google.bitcoin.bouncycastle.crypto.signers; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.CryptoException; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.Signer; +import com.google.bitcoin.bouncycastle.crypto.params.AsymmetricKeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.util.Arrays; + +public class GenericSigner + implements Signer +{ + private final AsymmetricBlockCipher engine; + private final Digest digest; + private boolean forSigning; + + public GenericSigner( + AsymmetricBlockCipher engine, + Digest digest) + { + this.engine = engine; + this.digest = digest; + } + + /** + * initialise the signer for signing or verification. + * + * @param forSigning + * true if for signing, false otherwise + * @param parameters + * necessary parameters. + */ + public void init( + boolean forSigning, + CipherParameters parameters) + { + this.forSigning = forSigning; + AsymmetricKeyParameter k; + + if (parameters instanceof ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).getParameters(); + } + else + { + k = (AsymmetricKeyParameter)parameters; + } + + if (forSigning && !k.isPrivate()) + { + throw new IllegalArgumentException("signing requires private key"); + } + + if (!forSigning && k.isPrivate()) + { + throw new IllegalArgumentException("verification requires public key"); + } + + reset(); + + engine.init(forSigning, parameters); + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte input) + { + digest.update(input); + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] input, + int inOff, + int length) + { + digest.update(input, inOff, length); + } + + /** + * Generate a signature for the message we've been loaded with using the key + * we were initialised with. + */ + public byte[] generateSignature() + throws CryptoException, DataLengthException + { + if (!forSigning) + { + throw new IllegalStateException("GenericSigner not initialised for signature generation."); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + return engine.processBlock(hash, 0, hash.length); + } + + /** + * return true if the internal state represents the signature described in + * the passed in array. + */ + public boolean verifySignature( + byte[] signature) + { + if (forSigning) + { + throw new IllegalStateException("GenericSigner not initialised for verification"); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + try + { + byte[] sig = engine.processBlock(signature, 0, signature.length); + + return Arrays.constantTimeAreEqual(sig, hash); + } + catch (Exception e) + { + return false; + } + } + + public void reset() + { + digest.reset(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/signers/ISO9796d2PSSSigner.java b/src/com/google/bitcoin/bouncycastle/crypto/signers/ISO9796d2PSSSigner.java new file mode 100644 index 000000000..648035a89 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/signers/ISO9796d2PSSSigner.java @@ -0,0 +1,608 @@ +package com.google.bitcoin.bouncycastle.crypto.signers; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.CryptoException; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.SignerWithRecovery; +import com.google.bitcoin.bouncycastle.crypto.digests.RIPEMD128Digest; +import com.google.bitcoin.bouncycastle.crypto.digests.RIPEMD160Digest; +import com.google.bitcoin.bouncycastle.crypto.digests.SHA1Digest; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithSalt; +import com.google.bitcoin.bouncycastle.crypto.params.RSAKeyParameters; + +import java.security.SecureRandom; + +/** + * ISO9796-2 - mechanism using a hash function with recovery (scheme 2 and 3). + *

    + * Note: the usual length for the salt is the length of the hash + * function used in bytes. + */ +public class ISO9796d2PSSSigner + implements SignerWithRecovery +{ + static final public int TRAILER_IMPLICIT = 0xBC; + static final public int TRAILER_RIPEMD160 = 0x31CC; + static final public int TRAILER_RIPEMD128 = 0x32CC; + static final public int TRAILER_SHA1 = 0x33CC; + + private Digest digest; + private AsymmetricBlockCipher cipher; + + private SecureRandom random; + private byte[] standardSalt; + + private int hLen; + private int trailer; + private int keyBits; + private byte[] block; + private byte[] mBuf; + private int messageLength; + private int saltLength; + private boolean fullMessage; + private byte[] recoveredMessage; + + /** + * Generate a signer for the with either implicit or explicit trailers + * for ISO9796-2, scheme 2 or 3. + * + * @param cipher base cipher to use for signature creation/verification + * @param digest digest to use. + * @param saltLength length of salt in bytes. + * @param implicit whether or not the trailer is implicit or gives the hash. + */ + public ISO9796d2PSSSigner( + AsymmetricBlockCipher cipher, + Digest digest, + int saltLength, + boolean implicit) + { + this.cipher = cipher; + this.digest = digest; + this.hLen = digest.getDigestSize(); + this.saltLength = saltLength; + + if (implicit) + { + trailer = TRAILER_IMPLICIT; + } + else + { + if (digest instanceof SHA1Digest) + { + trailer = TRAILER_SHA1; + } + else if (digest instanceof RIPEMD160Digest) + { + trailer = TRAILER_RIPEMD160; + } + else if (digest instanceof RIPEMD128Digest) + { + trailer = TRAILER_RIPEMD128; + } + else + { + throw new IllegalArgumentException("no valid trailer for digest"); + } + } + } + + /** + * Constructor for a signer with an explicit digest trailer. + * + * @param cipher cipher to use. + * @param digest digest to sign with. + * @param saltLength length of salt in bytes. + */ + public ISO9796d2PSSSigner( + AsymmetricBlockCipher cipher, + Digest digest, + int saltLength) + { + this(cipher, digest, saltLength, false); + } + + /** + * Initialise the signer. + * + * @param forSigning true if for signing, false if for verification. + * @param param parameters for signature generation/verification. If the + * parameters are for generation they should be a ParametersWithRandom, + * a ParametersWithSalt, or just an RSAKeyParameters object. If RSAKeyParameters + * are passed in a SecureRandom will be created. + * @exception IllegalArgumentException if wrong parameter type or a fixed + * salt is passed in which is the wrong length. + */ + public void init( + boolean forSigning, + CipherParameters param) + { + RSAKeyParameters kParam; + int lengthOfSalt = saltLength; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)param; + + kParam = (RSAKeyParameters)p.getParameters(); + if (forSigning) + { + random = p.getRandom(); + } + } + else if (param instanceof ParametersWithSalt) + { + ParametersWithSalt p = (ParametersWithSalt)param; + + kParam = (RSAKeyParameters)p.getParameters(); + standardSalt = p.getSalt(); + lengthOfSalt = standardSalt.length; + if (standardSalt.length != saltLength) + { + throw new IllegalArgumentException("Fixed salt is of wrong length"); + } + } + else + { + kParam = (RSAKeyParameters)param; + if (forSigning) + { + random = new SecureRandom(); + } + } + + cipher.init(forSigning, kParam); + + keyBits = kParam.getModulus().bitLength(); + + block = new byte[(keyBits + 7) / 8]; + + if (trailer == TRAILER_IMPLICIT) + { + mBuf = new byte[block.length - digest.getDigestSize() - lengthOfSalt - 1 - 1]; + } + else + { + mBuf = new byte[block.length - digest.getDigestSize() - lengthOfSalt - 1 - 2]; + } + + reset(); + } + + /** + * compare two byte arrays. + */ + private boolean isSameAs( + byte[] a, + byte[] b) + { + if (messageLength != b.length) + { + return false; + } + + for (int i = 0; i != b.length; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + + return true; + } + + /** + * clear possible sensitive data + */ + private void clearBlock( + byte[] block) + { + for (int i = 0; i != block.length; i++) + { + block[i] = 0; + } + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte b) + { + if (messageLength < mBuf.length) + { + mBuf[messageLength++] = b; + } + else + { + digest.update(b); + } + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] in, + int off, + int len) + { + while (len > 0 && messageLength < mBuf.length) + { + this.update(in[off]); + off++; + len--; + } + + if (len > 0) + { + digest.update(in, off, len); + } + } + + /** + * reset the internal state + */ + public void reset() + { + digest.reset(); + messageLength = 0; + if (mBuf != null) + { + clearBlock(mBuf); + } + if (recoveredMessage != null) + { + clearBlock(recoveredMessage); + recoveredMessage = null; + } + fullMessage = false; + } + + /** + * generate a signature for the loaded message using the key we were + * initialised with. + */ + public byte[] generateSignature() + throws CryptoException + { + int digSize = digest.getDigestSize(); + + byte[] m2Hash = new byte[digSize]; + + digest.doFinal(m2Hash, 0); + + byte[] C = new byte[8]; + LtoOSP(messageLength * 8, C); + + digest.update(C, 0, C.length); + + digest.update(mBuf, 0, messageLength); + + digest.update(m2Hash, 0, m2Hash.length); + + byte[] salt; + + if (standardSalt != null) + { + salt = standardSalt; + } + else + { + salt = new byte[saltLength]; + random.nextBytes(salt); + } + + digest.update(salt, 0, salt.length); + + byte[] hash = new byte[digest.getDigestSize()]; + + digest.doFinal(hash, 0); + + int tLength = 2; + if (trailer == TRAILER_IMPLICIT) + { + tLength = 1; + } + + int off = block.length - messageLength - salt.length - hLen - tLength - 1; + + block[off] = 0x01; + + System.arraycopy(mBuf, 0, block, off + 1, messageLength); + System.arraycopy(salt, 0, block, off + 1 + messageLength, salt.length); + + byte[] dbMask = maskGeneratorFunction1(hash, 0, hash.length, block.length - hLen - tLength); + for (int i = 0; i != dbMask.length; i++) + { + block[i] ^= dbMask[i]; + } + + System.arraycopy(hash, 0, block, block.length - hLen - tLength, hLen); + + if (trailer == TRAILER_IMPLICIT) + { + block[block.length - 1] = (byte)TRAILER_IMPLICIT; + } + else + { + block[block.length - 2] = (byte)(trailer >>> 8); + block[block.length - 1] = (byte)trailer; + } + + block[0] &= 0x7f; + + byte[] b = cipher.processBlock(block, 0, block.length); + + clearBlock(mBuf); + clearBlock(block); + messageLength = 0; + + return b; + } + + /** + * return true if the signature represents a ISO9796-2 signature + * for the passed in message. + */ + public boolean verifySignature( + byte[] signature) + { + byte[] block; + + try + { + block = cipher.processBlock(signature, 0, signature.length); + } + catch (Exception e) + { + return false; + } + + // + // adjust block size for leading zeroes if necessary + // + if (block.length < (keyBits + 7) / 8) + { + byte[] tmp = new byte[(keyBits + 7) / 8]; + + System.arraycopy(block, 0, tmp, tmp.length - block.length, block.length); + clearBlock(block); + block = tmp; + } + + int tLength; + + if (((block[block.length - 1] & 0xFF) ^ 0xBC) == 0) + { + tLength = 1; + } + else + { + int sigTrail = ((block[block.length - 2] & 0xFF) << 8) | (block[block.length - 1] & 0xFF); + + switch (sigTrail) + { + case TRAILER_RIPEMD160: + if (!(digest instanceof RIPEMD160Digest)) + { + throw new IllegalStateException("signer should be initialised with RIPEMD160"); + } + break; + case TRAILER_SHA1: + if (!(digest instanceof SHA1Digest)) + { + throw new IllegalStateException("signer should be initialised with SHA1"); + } + break; + case TRAILER_RIPEMD128: + if (!(digest instanceof RIPEMD128Digest)) + { + throw new IllegalStateException("signer should be initialised with RIPEMD128"); + } + break; + default: + throw new IllegalArgumentException("unrecognised hash in signature"); + } + + tLength = 2; + } + + // + // calculate H(m2) + // + byte[] m2Hash = new byte[hLen]; + digest.doFinal(m2Hash, 0); + + // + // remove the mask + // + byte[] dbMask = maskGeneratorFunction1(block, block.length - hLen - tLength, hLen, block.length - hLen - tLength); + for (int i = 0; i != dbMask.length; i++) + { + block[i] ^= dbMask[i]; + } + + block[0] &= 0x7f; + + // + // find out how much padding we've got + // + int mStart = 0; + for (; mStart != block.length; mStart++) + { + if (block[mStart] == 0x01) + { + break; + } + } + + mStart++; + + if (mStart >= block.length) + { + clearBlock(block); + return false; + } + + fullMessage = (mStart > 1); + + recoveredMessage = new byte[dbMask.length - mStart - saltLength]; + + System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length); + + // + // check the hashes + // + byte[] C = new byte[8]; + LtoOSP(recoveredMessage.length * 8, C); + + digest.update(C, 0, C.length); + + if (recoveredMessage.length != 0) + { + digest.update(recoveredMessage, 0, recoveredMessage.length); + } + + digest.update(m2Hash, 0, m2Hash.length); + + // Update for the salt + digest.update(block, mStart + recoveredMessage.length, saltLength); + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + int off = block.length - tLength - hash.length; + + for (int i = 0; i != hash.length; i++) + { + if (hash[i] != block[off + i]) + { + clearBlock(block); + clearBlock(hash); + clearBlock(recoveredMessage); + fullMessage = false; + + return false; + } + } + + clearBlock(block); + clearBlock(hash); + + // + // if they've input a message check what we've recovered against + // what was input. + // + if (messageLength != 0) + { + if (!isSameAs(mBuf, recoveredMessage)) + { + clearBlock(mBuf); + return false; + } + + messageLength = 0; + } + + clearBlock(mBuf); + return true; + } + + /** + * Return true if the full message was recoveredMessage. + * + * @return true on full message recovery, false otherwise, or if not sure. + * @see com.google.bitcoin.bouncycastle.crypto.SignerWithRecovery#hasFullMessage() + */ + public boolean hasFullMessage() + { + return fullMessage; + } + + /** + * Return a reference to the recoveredMessage message. + * + * @return the full/partial recoveredMessage message. + * @see com.google.bitcoin.bouncycastle.crypto.SignerWithRecovery#getRecoveredMessage() + */ + public byte[] getRecoveredMessage() + { + return recoveredMessage; + } + + /** + * int to octet string. + */ + private void ItoOSP( + int i, + byte[] sp) + { + sp[0] = (byte)(i >>> 24); + sp[1] = (byte)(i >>> 16); + sp[2] = (byte)(i >>> 8); + sp[3] = (byte)(i >>> 0); + } + + /** + * long to octet string. + */ + private void LtoOSP( + long l, + byte[] sp) + { + sp[0] = (byte)(l >>> 56); + sp[1] = (byte)(l >>> 48); + sp[2] = (byte)(l >>> 40); + sp[3] = (byte)(l >>> 32); + sp[4] = (byte)(l >>> 24); + sp[5] = (byte)(l >>> 16); + sp[6] = (byte)(l >>> 8); + sp[7] = (byte)(l >>> 0); + } + /** + * mask generator function, as described in PKCS1v2. + */ + private byte[] maskGeneratorFunction1( + byte[] Z, + int zOff, + int zLen, + int length) + { + byte[] mask = new byte[length]; + byte[] hashBuf = new byte[hLen]; + byte[] C = new byte[4]; + int counter = 0; + + digest.reset(); + + while (counter < (length / hLen)) + { + ItoOSP(counter, C); + + digest.update(Z, zOff, zLen); + digest.update(C, 0, C.length); + digest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * hLen, hLen); + + counter++; + } + + if ((counter * hLen) < length) + { + ItoOSP(counter, C); + + digest.update(Z, zOff, zLen); + digest.update(C, 0, C.length); + digest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * hLen, mask.length - (counter * hLen)); + } + + return mask; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/signers/ISO9796d2Signer.java b/src/com/google/bitcoin/bouncycastle/crypto/signers/ISO9796d2Signer.java new file mode 100644 index 000000000..0d7939347 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/signers/ISO9796d2Signer.java @@ -0,0 +1,487 @@ +package com.google.bitcoin.bouncycastle.crypto.signers; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.CryptoException; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.SignerWithRecovery; +import com.google.bitcoin.bouncycastle.crypto.digests.RIPEMD128Digest; +import com.google.bitcoin.bouncycastle.crypto.digests.RIPEMD160Digest; +import com.google.bitcoin.bouncycastle.crypto.digests.SHA1Digest; +import com.google.bitcoin.bouncycastle.crypto.params.RSAKeyParameters; + +/** + * ISO9796-2 - mechanism using a hash function with recovery (scheme 1) + */ +public class ISO9796d2Signer + implements SignerWithRecovery +{ + static final public int TRAILER_IMPLICIT = 0xBC; + static final public int TRAILER_RIPEMD160 = 0x31CC; + static final public int TRAILER_RIPEMD128 = 0x32CC; + static final public int TRAILER_SHA1 = 0x33CC; + + private Digest digest; + private AsymmetricBlockCipher cipher; + + private int trailer; + private int keyBits; + private byte[] block; + private byte[] mBuf; + private int messageLength; + private boolean fullMessage; + private byte[] recoveredMessage; + + /** + * Generate a signer for the with either implicit or explicit trailers + * for ISO9796-2. + * + * @param cipher base cipher to use for signature creation/verification + * @param digest digest to use. + * @param implicit whether or not the trailer is implicit or gives the hash. + */ + public ISO9796d2Signer( + AsymmetricBlockCipher cipher, + Digest digest, + boolean implicit) + { + this.cipher = cipher; + this.digest = digest; + + if (implicit) + { + trailer = TRAILER_IMPLICIT; + } + else + { + if (digest instanceof SHA1Digest) + { + trailer = TRAILER_SHA1; + } + else if (digest instanceof RIPEMD160Digest) + { + trailer = TRAILER_RIPEMD160; + } + else if (digest instanceof RIPEMD128Digest) + { + trailer = TRAILER_RIPEMD128; + } + else + { + throw new IllegalArgumentException("no valid trailer for digest"); + } + } + } + + /** + * Constructor for a signer with an explicit digest trailer. + * + * @param cipher cipher to use. + * @param digest digest to sign with. + */ + public ISO9796d2Signer( + AsymmetricBlockCipher cipher, + Digest digest) + { + this(cipher, digest, false); + } + + public void init( + boolean forSigning, + CipherParameters param) + { + RSAKeyParameters kParam = (RSAKeyParameters)param; + + cipher.init(forSigning, kParam); + + keyBits = kParam.getModulus().bitLength(); + + block = new byte[(keyBits + 7) / 8]; + + if (trailer == TRAILER_IMPLICIT) + { + mBuf = new byte[block.length - digest.getDigestSize() - 2]; + } + else + { + mBuf = new byte[block.length - digest.getDigestSize() - 3]; + } + + reset(); + } + + /** + * compare two byte arrays. + */ + private boolean isSameAs( + byte[] a, + byte[] b) + { + if (messageLength > mBuf.length) + { + if (mBuf.length > b.length) + { + return false; + } + + for (int i = 0; i != mBuf.length; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + } + else + { + if (messageLength != b.length) + { + return false; + } + + for (int i = 0; i != b.length; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + } + + return true; + } + + /** + * clear possible sensitive data + */ + private void clearBlock( + byte[] block) + { + for (int i = 0; i != block.length; i++) + { + block[i] = 0; + } + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte b) + { + digest.update(b); + + if (messageLength < mBuf.length) + { + mBuf[messageLength] = b; + } + + messageLength++; + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] in, + int off, + int len) + { + digest.update(in, off, len); + + if (messageLength < mBuf.length) + { + for (int i = 0; i < len && (i + messageLength) < mBuf.length; i++) + { + mBuf[messageLength + i] = in[off + i]; + } + } + + messageLength += len; + } + + /** + * reset the internal state + */ + public void reset() + { + digest.reset(); + messageLength = 0; + clearBlock(mBuf); + + if (recoveredMessage != null) + { + clearBlock(recoveredMessage); + } + + recoveredMessage = null; + fullMessage = false; + } + + /** + * generate a signature for the loaded message using the key we were + * initialised with. + */ + public byte[] generateSignature() + throws CryptoException + { + int digSize = digest.getDigestSize(); + + int t = 0; + int delta = 0; + + if (trailer == TRAILER_IMPLICIT) + { + t = 8; + delta = block.length - digSize - 1; + digest.doFinal(block, delta); + block[block.length - 1] = (byte)TRAILER_IMPLICIT; + } + else + { + t = 16; + delta = block.length - digSize - 2; + digest.doFinal(block, delta); + block[block.length - 2] = (byte)(trailer >>> 8); + block[block.length - 1] = (byte)trailer; + } + + byte header = 0; + int x = (digSize + messageLength) * 8 + t + 4 - keyBits; + + if (x > 0) + { + int mR = messageLength - ((x + 7) / 8); + header = 0x60; + + delta -= mR; + + System.arraycopy(mBuf, 0, block, delta, mR); + } + else + { + header = 0x40; + delta -= messageLength; + + System.arraycopy(mBuf, 0, block, delta, messageLength); + } + + if ((delta - 1) > 0) + { + for (int i = delta - 1; i != 0; i--) + { + block[i] = (byte)0xbb; + } + block[delta - 1] ^= (byte)0x01; + block[0] = (byte)0x0b; + block[0] |= header; + } + else + { + block[0] = (byte)0x0a; + block[0] |= header; + } + + byte[] b = cipher.processBlock(block, 0, block.length); + + clearBlock(mBuf); + clearBlock(block); + + return b; + } + + /** + * return true if the signature represents a ISO9796-2 signature + * for the passed in message. + */ + public boolean verifySignature( + byte[] signature) + { + byte[] block = null; + + try + { + block = cipher.processBlock(signature, 0, signature.length); + } + catch (Exception e) + { + return false; + } + + if (((block[0] & 0xC0) ^ 0x40) != 0) + { + clearBlock(mBuf); + clearBlock(block); + + return false; + } + + if (((block[block.length - 1] & 0xF) ^ 0xC) != 0) + { + clearBlock(mBuf); + clearBlock(block); + + return false; + } + + int delta = 0; + + if (((block[block.length - 1] & 0xFF) ^ 0xBC) == 0) + { + delta = 1; + } + else + { + int sigTrail = ((block[block.length - 2] & 0xFF) << 8) | (block[block.length - 1] & 0xFF); + + switch (sigTrail) + { + case TRAILER_RIPEMD160: + if (!(digest instanceof RIPEMD160Digest)) + { + throw new IllegalStateException("signer should be initialised with RIPEMD160"); + } + break; + case TRAILER_SHA1: + if (!(digest instanceof SHA1Digest)) + { + throw new IllegalStateException("signer should be initialised with SHA1"); + } + break; + case TRAILER_RIPEMD128: + if (!(digest instanceof RIPEMD128Digest)) + { + throw new IllegalStateException("signer should be initialised with RIPEMD128"); + } + break; + default: + throw new IllegalArgumentException("unrecognised hash in signature"); + } + + delta = 2; + } + + // + // find out how much padding we've got + // + int mStart = 0; + + for (mStart = 0; mStart != block.length; mStart++) + { + if (((block[mStart] & 0x0f) ^ 0x0a) == 0) + { + break; + } + } + + mStart++; + + // + // check the hashes + // + byte[] hash = new byte[digest.getDigestSize()]; + + int off = block.length - delta - hash.length; + + // + // there must be at least one byte of message string + // + if ((off - mStart) <= 0) + { + clearBlock(mBuf); + clearBlock(block); + + return false; + } + + // + // if we contain the whole message as well, check the hash of that. + // + if ((block[0] & 0x20) == 0) + { + fullMessage = true; + + digest.reset(); + digest.update(block, mStart, off - mStart); + digest.doFinal(hash, 0); + + for (int i = 0; i != hash.length; i++) + { + block[off + i] ^= hash[i]; + if (block[off + i] != 0) + { + clearBlock(mBuf); + clearBlock(block); + + return false; + } + } + + recoveredMessage = new byte[off - mStart]; + System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length); + } + else + { + fullMessage = false; + + digest.doFinal(hash, 0); + + for (int i = 0; i != hash.length; i++) + { + block[off + i] ^= hash[i]; + if (block[off + i] != 0) + { + clearBlock(mBuf); + clearBlock(block); + + return false; + } + } + + recoveredMessage = new byte[off - mStart]; + System.arraycopy(block, mStart, recoveredMessage, 0, recoveredMessage.length); + } + + // + // if they've input a message check what we've recovered against + // what was input. + // + if (messageLength != 0) + { + if (!isSameAs(mBuf, recoveredMessage)) + { + clearBlock(mBuf); + clearBlock(block); + + return false; + } + } + + clearBlock(mBuf); + clearBlock(block); + + return true; + } + + /** + * Return true if the full message was recoveredMessage. + * + * @return true on full message recovery, false otherwise. + * @see com.google.bitcoin.bouncycastle.crypto.SignerWithRecovery#hasFullMessage() + */ + public boolean hasFullMessage() + { + return fullMessage; + } + + /** + * Return a reference to the recoveredMessage message. + * + * @return the full/partial recoveredMessage message. + * @see com.google.bitcoin.bouncycastle.crypto.SignerWithRecovery#getRecoveredMessage() + */ + public byte[] getRecoveredMessage() + { + return recoveredMessage; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/signers/PSSSigner.java b/src/com/google/bitcoin/bouncycastle/crypto/signers/PSSSigner.java new file mode 100644 index 000000000..469261e43 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/signers/PSSSigner.java @@ -0,0 +1,337 @@ +package com.google.bitcoin.bouncycastle.crypto.signers; + +import com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.CryptoException; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.Signer; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.crypto.params.RSABlindingParameters; +import com.google.bitcoin.bouncycastle.crypto.params.RSAKeyParameters; + +import java.security.SecureRandom; + +/** + * RSA-PSS as described in PKCS# 1 v 2.1. + *

    + * Note: the usual value for the salt length is the number of + * bytes in the hash function. + */ +public class PSSSigner + implements Signer +{ + static final public byte TRAILER_IMPLICIT = (byte)0xBC; + + private Digest contentDigest; + private Digest mgfDigest; + private AsymmetricBlockCipher cipher; + private SecureRandom random; + + private int hLen; + private int sLen; + private int emBits; + private byte[] salt; + private byte[] mDash; + private byte[] block; + private byte trailer; + + /** + * basic constructor + * + * @param cipher the asymmetric cipher to use. + * @param digest the digest to use. + * @param sLen the length of the salt to use (in bytes). + */ + public PSSSigner( + AsymmetricBlockCipher cipher, + Digest digest, + int sLen) + { + this(cipher, digest, sLen, TRAILER_IMPLICIT); + } + + public PSSSigner( + AsymmetricBlockCipher cipher, + Digest digest, + int sLen, + byte trailer) + { + this(cipher, digest, digest, sLen, trailer); + } + + public PSSSigner( + AsymmetricBlockCipher cipher, + Digest contentDigest, + Digest mgfDigest, + int sLen, + byte trailer) + { + this.cipher = cipher; + this.contentDigest = contentDigest; + this.mgfDigest = mgfDigest; + this.hLen = mgfDigest.getDigestSize(); + this.sLen = sLen; + this.salt = new byte[sLen]; + this.mDash = new byte[8 + sLen + hLen]; + this.trailer = trailer; + } + + public void init( + boolean forSigning, + CipherParameters param) + { + CipherParameters params; + + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom p = (ParametersWithRandom)param; + + params = p.getParameters(); + random = p.getRandom(); + } + else + { + params = param; + if (forSigning) + { + random = new SecureRandom(); + } + } + + cipher.init(forSigning, params); + + RSAKeyParameters kParam; + + if (params instanceof RSABlindingParameters) + { + kParam = ((RSABlindingParameters)params).getPublicKey(); + } + else + { + kParam = (RSAKeyParameters)params; + } + + emBits = kParam.getModulus().bitLength() - 1; + + if (emBits < (8 * hLen + 8 * sLen + 9)) + { + throw new IllegalArgumentException("key too small for specified hash and salt lengths"); + } + + block = new byte[(emBits + 7) / 8]; + + reset(); + } + + /** + * clear possible sensitive data + */ + private void clearBlock( + byte[] block) + { + for (int i = 0; i != block.length; i++) + { + block[i] = 0; + } + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte b) + { + contentDigest.update(b); + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] in, + int off, + int len) + { + contentDigest.update(in, off, len); + } + + /** + * reset the internal state + */ + public void reset() + { + contentDigest.reset(); + } + + /** + * generate a signature for the message we've been loaded with using + * the key we were initialised with. + */ + public byte[] generateSignature() + throws CryptoException, DataLengthException + { + contentDigest.doFinal(mDash, mDash.length - hLen - sLen); + + if (sLen != 0) + { + random.nextBytes(salt); + + System.arraycopy(salt, 0, mDash, mDash.length - sLen, sLen); + } + + byte[] h = new byte[hLen]; + + mgfDigest.update(mDash, 0, mDash.length); + + mgfDigest.doFinal(h, 0); + + block[block.length - sLen - 1 - hLen - 1] = 0x01; + System.arraycopy(salt, 0, block, block.length - sLen - hLen - 1, sLen); + + byte[] dbMask = maskGeneratorFunction1(h, 0, h.length, block.length - hLen - 1); + for (int i = 0; i != dbMask.length; i++) + { + block[i] ^= dbMask[i]; + } + + block[0] &= (0xff >> ((block.length * 8) - emBits)); + + System.arraycopy(h, 0, block, block.length - hLen - 1, hLen); + + block[block.length - 1] = trailer; + + byte[] b = cipher.processBlock(block, 0, block.length); + + clearBlock(block); + + return b; + } + + /** + * return true if the internal state represents the signature described + * in the passed in array. + */ + public boolean verifySignature( + byte[] signature) + { + contentDigest.doFinal(mDash, mDash.length - hLen - sLen); + + try + { + byte[] b = cipher.processBlock(signature, 0, signature.length); + System.arraycopy(b, 0, block, block.length - b.length, b.length); + } + catch (Exception e) + { + return false; + } + + if (block[block.length - 1] != trailer) + { + clearBlock(block); + return false; + } + + byte[] dbMask = maskGeneratorFunction1(block, block.length - hLen - 1, hLen, block.length - hLen - 1); + + for (int i = 0; i != dbMask.length; i++) + { + block[i] ^= dbMask[i]; + } + + block[0] &= (0xff >> ((block.length * 8) - emBits)); + + for (int i = 0; i != block.length - hLen - sLen - 2; i++) + { + if (block[i] != 0) + { + clearBlock(block); + return false; + } + } + + if (block[block.length - hLen - sLen - 2] != 0x01) + { + clearBlock(block); + return false; + } + + System.arraycopy(block, block.length - sLen - hLen - 1, mDash, mDash.length - sLen, sLen); + + mgfDigest.update(mDash, 0, mDash.length); + mgfDigest.doFinal(mDash, mDash.length - hLen); + + for (int i = block.length - hLen - 1, j = mDash.length - hLen; + j != mDash.length; i++, j++) + { + if ((block[i] ^ mDash[j]) != 0) + { + clearBlock(mDash); + clearBlock(block); + return false; + } + } + + clearBlock(mDash); + clearBlock(block); + + return true; + } + + /** + * int to octet string. + */ + private void ItoOSP( + int i, + byte[] sp) + { + sp[0] = (byte)(i >>> 24); + sp[1] = (byte)(i >>> 16); + sp[2] = (byte)(i >>> 8); + sp[3] = (byte)(i >>> 0); + } + + /** + * mask generator function, as described in PKCS1v2. + */ + private byte[] maskGeneratorFunction1( + byte[] Z, + int zOff, + int zLen, + int length) + { + byte[] mask = new byte[length]; + byte[] hashBuf = new byte[hLen]; + byte[] C = new byte[4]; + int counter = 0; + + mgfDigest.reset(); + + while (counter < (length / hLen)) + { + ItoOSP(counter, C); + + mgfDigest.update(Z, zOff, zLen); + mgfDigest.update(C, 0, C.length); + mgfDigest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * hLen, hLen); + + counter++; + } + + if ((counter * hLen) < length) + { + ItoOSP(counter, C); + + mgfDigest.update(Z, zOff, zLen); + mgfDigest.update(C, 0, C.length); + mgfDigest.doFinal(hashBuf, 0); + + System.arraycopy(hashBuf, 0, mask, counter * hLen, mask.length - (counter * hLen)); + } + + return mask; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/signers/RSADigestSigner.java b/src/com/google/bitcoin/bouncycastle/crypto/signers/RSADigestSigner.java new file mode 100644 index 000000000..96570ab2d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/signers/RSADigestSigner.java @@ -0,0 +1,230 @@ +package com.google.bitcoin.bouncycastle.crypto.signers; + +import com.google.bitcoin.bouncycastle.asn1.DERNull; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.nist.NISTObjectIdentifiers; +import com.google.bitcoin.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import com.google.bitcoin.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; +import com.google.bitcoin.bouncycastle.asn1.x509.DigestInfo; +import com.google.bitcoin.bouncycastle.asn1.x509.X509ObjectIdentifiers; +import com.google.bitcoin.bouncycastle.crypto.AsymmetricBlockCipher; +import com.google.bitcoin.bouncycastle.crypto.CipherParameters; +import com.google.bitcoin.bouncycastle.crypto.CryptoException; +import com.google.bitcoin.bouncycastle.crypto.DataLengthException; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.Signer; +import com.google.bitcoin.bouncycastle.crypto.encodings.PKCS1Encoding; +import com.google.bitcoin.bouncycastle.crypto.engines.RSABlindedEngine; +import com.google.bitcoin.bouncycastle.crypto.params.AsymmetricKeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; + +import java.util.Hashtable; + +public class RSADigestSigner + implements Signer +{ + private final AsymmetricBlockCipher rsaEngine = new PKCS1Encoding(new RSABlindedEngine()); + private final AlgorithmIdentifier algId; + private final Digest digest; + private boolean forSigning; + + private static final Hashtable oidMap = new Hashtable(); + + /* + * Load OID table. + */ + static + { + oidMap.put("RIPEMD128", TeleTrusTObjectIdentifiers.ripemd128); + oidMap.put("RIPEMD160", TeleTrusTObjectIdentifiers.ripemd160); + oidMap.put("RIPEMD256", TeleTrusTObjectIdentifiers.ripemd256); + + oidMap.put("SHA-1", X509ObjectIdentifiers.id_SHA1); + oidMap.put("SHA-224", NISTObjectIdentifiers.id_sha224); + oidMap.put("SHA-256", NISTObjectIdentifiers.id_sha256); + oidMap.put("SHA-384", NISTObjectIdentifiers.id_sha384); + oidMap.put("SHA-512", NISTObjectIdentifiers.id_sha512); + + oidMap.put("MD2", PKCSObjectIdentifiers.md2); + oidMap.put("MD4", PKCSObjectIdentifiers.md4); + oidMap.put("MD5", PKCSObjectIdentifiers.md5); + } + + public RSADigestSigner( + Digest digest) + { + this.digest = digest; + + algId = new AlgorithmIdentifier((DERObjectIdentifier)oidMap.get(digest.getAlgorithmName()), DERNull.INSTANCE); + } + + /** + * @deprecated + */ + public String getAlgorithmName() + { + return digest.getAlgorithmName() + "withRSA"; + } + + /** + * initialise the signer for signing or verification. + * + * @param forSigning + * true if for signing, false otherwise + * @param parameters + * necessary parameters. + */ + public void init( + boolean forSigning, + CipherParameters parameters) + { + this.forSigning = forSigning; + AsymmetricKeyParameter k; + + if (parameters instanceof ParametersWithRandom) + { + k = (AsymmetricKeyParameter)((ParametersWithRandom)parameters).getParameters(); + } + else + { + k = (AsymmetricKeyParameter)parameters; + } + + if (forSigning && !k.isPrivate()) + { + throw new IllegalArgumentException("signing requires private key"); + } + + if (!forSigning && k.isPrivate()) + { + throw new IllegalArgumentException("verification requires public key"); + } + + reset(); + + rsaEngine.init(forSigning, parameters); + } + + /** + * update the internal digest with the byte b + */ + public void update( + byte input) + { + digest.update(input); + } + + /** + * update the internal digest with the byte array in + */ + public void update( + byte[] input, + int inOff, + int length) + { + digest.update(input, inOff, length); + } + + /** + * Generate a signature for the message we've been loaded with using the key + * we were initialised with. + */ + public byte[] generateSignature() + throws CryptoException, DataLengthException + { + if (!forSigning) + { + throw new IllegalStateException("RSADigestSigner not initialised for signature generation."); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + byte[] data = derEncode(hash); + return rsaEngine.processBlock(data, 0, data.length); + } + + /** + * return true if the internal state represents the signature described in + * the passed in array. + */ + public boolean verifySignature( + byte[] signature) + { + if (forSigning) + { + throw new IllegalStateException("RSADigestSigner not initialised for verification"); + } + + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + + byte[] sig; + byte[] expected; + + try + { + sig = rsaEngine.processBlock(signature, 0, signature.length); + expected = derEncode(hash); + } + catch (Exception e) + { + return false; + } + + if (sig.length == expected.length) + { + for (int i = 0; i < sig.length; i++) + { + if (sig[i] != expected[i]) + { + return false; + } + } + } + else if (sig.length == expected.length - 2) // NULL left out + { + int sigOffset = sig.length - hash.length - 2; + int expectedOffset = expected.length - hash.length - 2; + + expected[1] -= 2; // adjust lengths + expected[3] -= 2; + + for (int i = 0; i < hash.length; i++) + { + if (sig[sigOffset + i] != expected[expectedOffset + i]) // check hash + { + return false; + } + } + + for (int i = 0; i < sigOffset; i++) + { + if (sig[i] != expected[i]) // check header less NULL + { + return false; + } + } + } + else + { + return false; + } + + return true; + } + + public void reset() + { + digest.reset(); + } + + private byte[] derEncode( + byte[] hash) + { + DigestInfo dInfo = new DigestInfo(algId, hash); + + return dInfo.getDEREncoded(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/AlwaysValidVerifyer.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/AlwaysValidVerifyer.java new file mode 100644 index 000000000..1a45b2df1 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/AlwaysValidVerifyer.java @@ -0,0 +1,24 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +import com.google.bitcoin.bouncycastle.asn1.x509.X509CertificateStructure; + +/** + * A certificate verifyer, that will always return true. + *

    + * DO NOT USE THIS FILE UNLESS YOU KNOW EXACTLY WHAT YOU ARE DOING.
    + * 
    + */ +public class AlwaysValidVerifyer implements CertificateVerifyer +{ + + /** + * Return true. + * + * @see com.google.bitcoin.bouncycastle.crypto.tls.CertificateVerifyer#isValid(com.google.bitcoin.bouncycastle.asn1.x509.X509CertificateStructure[]) + */ + public boolean isValid(X509CertificateStructure[] certs) + { + return true; + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/ByteQueue.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/ByteQueue.java new file mode 100644 index 000000000..a126e03f2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/ByteQueue.java @@ -0,0 +1,131 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +/** + * A queue for bytes. + *

    + * This file could be more optimized. + *

    + */ +public class ByteQueue +{ + + /** + * @return The smallest number which can be written as 2^x which is + * bigger than i. + */ + public static final int nextTwoPow(int i) + { + /* + * This code is based of a lot of code I found on the Internet + * which mostly referenced a book called "Hacking delight". + * + */ + i |= (i >> 1); + i |= (i >> 2); + i |= (i >> 4); + i |= (i >> 8); + i |= (i >> 16); + return i + 1; + } + + /** + * The initial size for our buffer. + */ + private static final int INITBUFSIZE = 1024; + + /** + * The buffer where we store our data. + */ + private byte[] databuf = new byte[ByteQueue.INITBUFSIZE]; + + /** + * How many bytes at the beginning of the buffer are skipped. + */ + private int skipped = 0; + + /** + * How many bytes in the buffer are valid data. + */ + private int available = 0; + + /** + * Read data from the buffer. + * + * @param buf The buffer where the read data will be copied to. + * @param offset How many bytes to skip at the beginning of buf. + * @param len How many bytes to read at all. + * @param skip How many bytes from our data to skip. + */ + public void read(byte[] buf, int offset, int len, int skip) + { + if ((available - skip) < len) + { + throw new TlsRuntimeException("Not enough data to read"); + } + if ((buf.length - offset) < len) + { + throw new TlsRuntimeException("Buffer size of " + buf.length + " is too small for a read of " + len + " bytes"); + } + System.arraycopy(databuf, skipped + skip, buf, offset, len); + return; + } + + + /** + * Add some data to our buffer. + * + * @param data A byte-array to read data from. + * @param offset How many bytes to skip at the beginning of the array. + * @param len How many bytes to read from the array. + */ + public void addData(byte[] data, int offset, int len) + { + if ((skipped + available + len) > databuf.length) + { + byte[] tmp = new byte[ByteQueue.nextTwoPow(data.length)]; + System.arraycopy(databuf, skipped, tmp, 0, available); + skipped = 0; + databuf = tmp; + } + System.arraycopy(data, offset, databuf, skipped + available, len); + available += len; + } + + /** + * Remove some bytes from our data from the beginning. + * + * @param i How many bytes to remove. + */ + public void removeData(int i) + { + if (i > available) + { + throw new TlsRuntimeException("Cannot remove " + i + " bytes, only got " + available); + } + + /* + * Skip the data. + */ + available -= i; + skipped += i; + + /* + * If more than half of our data is skipped, we will move the data + * in the buffer. + */ + if (skipped > (databuf.length / 2)) + { + System.arraycopy(databuf, skipped, databuf, 0, available); + skipped = 0; + } + } + + /** + * @return The number of bytes which are available in this buffer. + */ + public int size() + { + return available; + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/Certificate.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/Certificate.java new file mode 100644 index 000000000..e3e2b6924 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/Certificate.java @@ -0,0 +1,77 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +import com.google.bitcoin.bouncycastle.asn1.ASN1InputStream; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.x509.X509CertificateStructure; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; + +/** + * A representation for a certificate chain as used by an tls server. + */ +public class Certificate +{ + /** + * The certificates. + */ + protected X509CertificateStructure[] certs; + + /** + * Parse the ServerCertificate message. + * + * @param is The stream where to parse from. + * @return A Certificate object with the certs, the server has sended. + * @throws IOException If something goes wrong during parsing. + */ + protected static Certificate parse(InputStream is) throws IOException + { + X509CertificateStructure[] certs; + int left = TlsUtils.readUint24(is); + Vector tmp = new Vector(); + while (left > 0) + { + int size = TlsUtils.readUint24(is); + left -= 3 + size; + byte[] buf = new byte[size]; + TlsUtils.readFully(buf, is); + ByteArrayInputStream bis = new ByteArrayInputStream(buf); + ASN1InputStream ais = new ASN1InputStream(bis); + DERObject o = ais.readObject(); + tmp.addElement(X509CertificateStructure.getInstance(o)); + if (bis.available() > 0) + { + throw new IllegalArgumentException("Sorry, there is garbage data left after the certificate"); + } + } + certs = new X509CertificateStructure[tmp.size()]; + for (int i = 0; i < tmp.size(); i++) + { + certs[i] = (X509CertificateStructure)tmp.elementAt(i); + } + return new Certificate(certs); + } + + /** + * Private constructure from an cert array. + * + * @param certs The certs the chain should contain. + */ + private Certificate(X509CertificateStructure[] certs) + { + this.certs = certs; + } + + /** + * @return An array which contains the certs, this chain contains. + */ + public X509CertificateStructure[] getCerts() + { + X509CertificateStructure[] result = new X509CertificateStructure[certs.length]; + System.arraycopy(certs, 0, result, 0, certs.length); + return result; + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/CertificateVerifyer.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/CertificateVerifyer.java new file mode 100644 index 000000000..35d2dd93c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/CertificateVerifyer.java @@ -0,0 +1,16 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +import com.google.bitcoin.bouncycastle.asn1.x509.X509CertificateStructure; + +/** + * This should be implemented by any class which can find out, if a given + * certificate chain is beeing accepted by an client. + */ +public interface CertificateVerifyer +{ + /** + * @param certs The certs, which are part of the chain. + * @return True, if the chain is accepted, false otherwise. + */ + public boolean isValid(X509CertificateStructure[] certs); +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/CombinedHash.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/CombinedHash.java new file mode 100644 index 000000000..4dc09414e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/CombinedHash.java @@ -0,0 +1,68 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.digests.MD5Digest; +import com.google.bitcoin.bouncycastle.crypto.digests.SHA1Digest; + +/** + * A combined hash, which implements md5(m) || sha1(m). + */ +public class CombinedHash implements Digest +{ + private Digest md5 = new MD5Digest(); + private Digest sha1 = new SHA1Digest(); + + /** + * @see com.google.bitcoin.bouncycastle.crypto.Digest#getAlgorithmName() + */ + public String getAlgorithmName() + { + return md5.getAlgorithmName() + " and " + sha1.getAlgorithmName() + " for TLS 1.0"; + } + + /** + * @see com.google.bitcoin.bouncycastle.crypto.Digest#getDigestSize() + */ + public int getDigestSize() + { + return 16 + 20; + } + + /** + * @see com.google.bitcoin.bouncycastle.crypto.Digest#update(byte) + */ + public void update(byte in) + { + md5.update(in); + sha1.update(in); + } + + /** + * @see com.google.bitcoin.bouncycastle.crypto.Digest#update(byte[],int,int) + */ + public void update(byte[] in, int inOff, int len) + { + md5.update(in, inOff, len); + sha1.update(in, inOff, len); + } + + /** + * @see com.google.bitcoin.bouncycastle.crypto.Digest#doFinal(byte[],int) + */ + public int doFinal(byte[] out, int outOff) + { + int i1 = md5.doFinal(out, outOff); + int i2 = sha1.doFinal(out, outOff + 16); + return i1 + i2; + } + + /** + * @see com.google.bitcoin.bouncycastle.crypto.Digest#reset() + */ + public void reset() + { + md5.reset(); + sha1.reset(); + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/RecordStream.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/RecordStream.java new file mode 100644 index 000000000..7aaa6669a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/RecordStream.java @@ -0,0 +1,99 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * An implementation of the TLS 1.0 record layer. + */ +public class RecordStream +{ + + private TlsProtocolHandler handler; + private InputStream is; + private OutputStream os; + protected CombinedHash hash1; + protected CombinedHash hash2; + protected TlsCipherSuite readSuite = null; + protected TlsCipherSuite writeSuite = null; + + + protected RecordStream(TlsProtocolHandler handler, InputStream is, OutputStream os) + { + this.handler = handler; + this.is = is; + this.os = os; + hash1 = new CombinedHash(); + hash2 = new CombinedHash(); + this.readSuite = new TlsNullCipherSuite(); + this.writeSuite = this.readSuite; + } + + public void readData() throws IOException + { + short type = TlsUtils.readUint8(is); + TlsUtils.checkVersion(is, handler); + int size = TlsUtils.readUint16(is); + byte[] buf = decodeAndVerify(type, is, size); + handler.processData(type, buf, 0, buf.length); + + } + + protected byte[] decodeAndVerify(short type, InputStream is, int len) throws IOException + { + byte[] buf = new byte[len]; + TlsUtils.readFully(buf, is); + byte[] result = readSuite.decodeCiphertext(type, buf, 0, buf.length, handler); + return result; + } + + protected void writeMessage(short type, byte[] message, int offset, int len) throws IOException + { + if (type == 22) // TlsProtocolHandler.RL_HANDSHAKE + { + hash1.update(message, offset, len); + hash2.update(message, offset, len); + } + byte[] ciphertext = writeSuite.encodePlaintext(type, message, offset, len); + byte[] writeMessage = new byte[ciphertext.length + 5]; + TlsUtils.writeUint8(type, writeMessage, 0); + TlsUtils.writeUint8((short)3, writeMessage, 1); + TlsUtils.writeUint8((short)1, writeMessage, 2); + TlsUtils.writeUint16(ciphertext.length, writeMessage, 3); + System.arraycopy(ciphertext, 0, writeMessage, 5, ciphertext.length); + os.write(writeMessage); + os.flush(); + } + + protected void close() throws IOException + { + IOException e = null; + try + { + is.close(); + } + catch (IOException ex) + { + e = ex; + } + try + { + os.close(); + } + catch (IOException ex) + { + e = ex; + } + if (e != null) + { + throw e; + } + } + + protected void flush() throws IOException + { + os.flush(); + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsBlockCipherCipherSuite.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsBlockCipherCipherSuite.java new file mode 100644 index 000000000..db418582c --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsBlockCipherCipherSuite.java @@ -0,0 +1,190 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +import com.google.bitcoin.bouncycastle.crypto.BlockCipher; +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithIV; + +import java.io.IOException; + +/** + * A generic TLS 1.0 block cipher suite. This can be used for AES or 3DES for + * example. + */ +public class TlsBlockCipherCipherSuite extends TlsCipherSuite +{ + + private BlockCipher encryptCipher; + + private BlockCipher decryptCipher; + + private Digest writeDigest; + + private Digest readDigest; + + private int cipherKeySize; + + private short keyExchange; + + private TlsMac writeMac; + + private TlsMac readMac; + + protected TlsBlockCipherCipherSuite(BlockCipher encrypt, + BlockCipher decrypt, Digest writeDigest, Digest readDigest, + int cipherKeySize, short keyExchange) + { + this.encryptCipher = encrypt; + this.decryptCipher = decrypt; + this.writeDigest = writeDigest; + this.readDigest = readDigest; + this.cipherKeySize = cipherKeySize; + this.keyExchange = keyExchange; + } + + protected void init(byte[] ms, byte[] cr, byte[] sr) + { + int prfSize = (2 * cipherKeySize) + (2 * writeDigest.getDigestSize()) + + (2 * encryptCipher.getBlockSize()); + byte[] key_block = new byte[prfSize]; + byte[] random = new byte[cr.length + sr.length]; + System.arraycopy(cr, 0, random, sr.length, cr.length); + System.arraycopy(sr, 0, random, 0, sr.length); + TlsUtils.PRF(ms, TlsUtils.toByteArray("key expansion"), random, key_block); + + int offset = 0; + + // Init MACs + writeMac = new TlsMac(writeDigest, key_block, offset, writeDigest + .getDigestSize()); + offset += writeDigest.getDigestSize(); + readMac = new TlsMac(readDigest, key_block, offset, readDigest + .getDigestSize()); + offset += readDigest.getDigestSize(); + + // Init Ciphers + this.initCipher(true, encryptCipher, key_block, cipherKeySize, offset, + offset + (cipherKeySize * 2)); + offset += cipherKeySize; + this.initCipher(false, decryptCipher, key_block, cipherKeySize, offset, + offset + cipherKeySize + decryptCipher.getBlockSize()); + } + + private void initCipher(boolean forEncryption, BlockCipher cipher, + byte[] key_block, int key_size, int key_offset, int iv_offset) + { + KeyParameter key_parameter = new KeyParameter(key_block, key_offset, + key_size); + ParametersWithIV parameters_with_iv = new ParametersWithIV( + key_parameter, key_block, iv_offset, cipher.getBlockSize()); + cipher.init(forEncryption, parameters_with_iv); + } + + protected byte[] encodePlaintext(short type, byte[] plaintext, int offset, + int len) + { + int blocksize = encryptCipher.getBlockSize(); + int paddingsize = blocksize + - ((len + writeMac.getSize() + 1) % blocksize); + int totalsize = len + writeMac.getSize() + paddingsize + 1; + byte[] outbuf = new byte[totalsize]; + System.arraycopy(plaintext, offset, outbuf, 0, len); + byte[] mac = writeMac.calculateMac(type, plaintext, offset, len); + System.arraycopy(mac, 0, outbuf, len, mac.length); + int paddoffset = len + mac.length; + for (int i = 0; i <= paddingsize; i++) + { + outbuf[i + paddoffset] = (byte)paddingsize; + } + for (int i = 0; i < totalsize; i += blocksize) + { + encryptCipher.processBlock(outbuf, i, outbuf, i); + } + return outbuf; + + } + + protected byte[] decodeCiphertext(short type, byte[] ciphertext, + int offset, int len, TlsProtocolHandler handler) throws IOException + { + int blocksize = decryptCipher.getBlockSize(); + boolean decrypterror = false; + + /* + * Decrypt all the ciphertext using the blockcipher + */ + for (int i = 0; i < len; i += blocksize) + { + decryptCipher.processBlock(ciphertext, i + offset, ciphertext, i + + offset); + } + + /* + * Check if padding is correct + */ + int paddingsize = ciphertext[offset + len - 1]; + if (offset + len - 1 - paddingsize < 0) + { + /* + * This would lead to a negative array index, so this padding + * must be incorrect! + */ + decrypterror = true; + paddingsize = 0; + } + else + { + /* + * Now, check all the padding-bytes. + */ + for (int i = 0; i <= paddingsize; i++) + { + if (ciphertext[offset + len - 1 - i] != paddingsize) + { + /* Wrong padding */ + decrypterror = true; + } + } + } + + /* + * We now don't care if padding verification has failed or not, + * we will calculate the mac to give an attacker no kind of timing + * profile he can use to find out if mac verification failed or + * padding verification failed. + */ + int plaintextlength = len - readMac.getSize() - paddingsize - 1; + byte[] calculatedMac = readMac.calculateMac(type, ciphertext, offset, + plaintextlength); + + /* + * Check all bytes in the mac. + */ + for (int i = 0; i < calculatedMac.length; i++) + { + if (ciphertext[offset + plaintextlength + i] != calculatedMac[i]) + { + decrypterror = true; + } + } + + /* + * Now, it is safe to fail. + */ + if (decrypterror) + { + handler.failWithError(TlsProtocolHandler.AL_fatal, + TlsProtocolHandler.AP_bad_record_mac); + } + byte[] plaintext = new byte[plaintextlength]; + System.arraycopy(ciphertext, offset, plaintext, 0, plaintextlength); + return plaintext; + + } + + protected short getKeyExchangeAlgorithm() + { + return this.keyExchange; + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsCipherSuite.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsCipherSuite.java new file mode 100644 index 000000000..8f4c8b9a5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsCipherSuite.java @@ -0,0 +1,32 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +import java.io.IOException; + +/** + * A generic class for ciphersuites in TLS 1.0. + */ +public abstract class TlsCipherSuite +{ + + protected static final short KE_RSA = 1; + protected static final short KE_RSA_EXPORT = 2; + protected static final short KE_DHE_DSS = 3; + protected static final short KE_DHE_DSS_EXPORT = 4; + protected static final short KE_DHE_RSA = 5; + protected static final short KE_DHE_RSA_EXPORT = 6; + protected static final short KE_DH_DSS = 7; + protected static final short KE_DH_RSA = 8; + protected static final short KE_DH_anon = 9; + protected static final short KE_SRP = 10; + protected static final short KE_SRP_RSA = 11; + protected static final short KE_SRP_DSS = 12; + + protected abstract void init(byte[] ms, byte[] cr, byte[] sr); + + protected abstract byte[] encodePlaintext(short type, byte[] plaintext, int offset, int len); + + protected abstract byte[] decodeCiphertext(short type, byte[] plaintext, int offset, int len, TlsProtocolHandler handler) throws IOException; + + protected abstract short getKeyExchangeAlgorithm(); + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsCipherSuiteManager.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsCipherSuiteManager.java new file mode 100644 index 000000000..7e58f91b6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsCipherSuiteManager.java @@ -0,0 +1,158 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +import com.google.bitcoin.bouncycastle.crypto.digests.SHA1Digest; +import com.google.bitcoin.bouncycastle.crypto.engines.AESFastEngine; +import com.google.bitcoin.bouncycastle.crypto.engines.DESedeEngine; +import com.google.bitcoin.bouncycastle.crypto.modes.CBCBlockCipher; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * A manager for ciphersuite. This class does manage all ciphersuites + * which are used by MicroTLS. + */ +public class TlsCipherSuiteManager +{ + private static final int TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000a; + private static final int TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013; + private static final int TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016; + private static final int TLS_RSA_WITH_AES_128_CBC_SHA = 0x002f; + private static final int TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032; + private static final int TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033; + private static final int TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035; + private static final int TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038; + private static final int TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039; + +// private static final int TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0xC01A; +// private static final int TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = 0xC01B; +// private static final int TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA = 0xC01C; +// private static final int TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0xC01D; +// private static final int TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = 0xC01E; +// private static final int TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA = 0xC01F; +// private static final int TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0xC020; +// private static final int TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xC021; +// private static final int TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA = 0xC022; + + protected static void writeCipherSuites(OutputStream os) throws IOException + { + int[] suites = new int[] + { + TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + TLS_RSA_WITH_AES_256_CBC_SHA, + TLS_RSA_WITH_AES_128_CBC_SHA, + TLS_RSA_WITH_3DES_EDE_CBC_SHA, + +// TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA, +// TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA, +// TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA, +// TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA, +// TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, +// TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA, +// TLS_SRP_SHA_WITH_AES_256_CBC_SHA, +// TLS_SRP_SHA_WITH_AES_128_CBC_SHA, +// TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA, + }; + + TlsUtils.writeUint16(2 * suites.length, os); + for (int i = 0; i < suites.length; ++i) + { + TlsUtils.writeUint16(suites[i], os); + } + } + + protected static TlsCipherSuite getCipherSuite(int number, TlsProtocolHandler handler) throws IOException + { + switch (number) + { + case TLS_RSA_WITH_3DES_EDE_CBC_SHA: + return createDESedeCipherSuite(24, TlsCipherSuite.KE_RSA); + + case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + return createDESedeCipherSuite(24, TlsCipherSuite.KE_DHE_DSS); + + case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + return createDESedeCipherSuite(24, TlsCipherSuite.KE_DHE_RSA); + + case TLS_RSA_WITH_AES_128_CBC_SHA: + return createAESCipherSuite(16, TlsCipherSuite.KE_RSA); + + case TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + return createAESCipherSuite(16, TlsCipherSuite.KE_DHE_DSS); + + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + return createAESCipherSuite(16, TlsCipherSuite.KE_DHE_RSA); + + case TLS_RSA_WITH_AES_256_CBC_SHA: + return createAESCipherSuite(32, TlsCipherSuite.KE_RSA); + + case TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + return createAESCipherSuite(32, TlsCipherSuite.KE_DHE_DSS); + + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + return createAESCipherSuite(32, TlsCipherSuite.KE_DHE_RSA); + +// case TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA: +// return createDESedeCipherSuite(24, TlsCipherSuite.KE_SRP); +// +// case TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA: +// return createDESedeCipherSuite(24, TlsCipherSuite.KE_SRP_RSA); +// +// case TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA: +// return createDESedeCipherSuite(24, TlsCipherSuite.KE_SRP_DSS); +// +// case TLS_SRP_SHA_WITH_AES_128_CBC_SHA: +// return createAESCipherSuite(16, TlsCipherSuite.KE_SRP); +// +// case TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA: +// return createAESCipherSuite(16, TlsCipherSuite.KE_SRP_RSA); +// +// case TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA: +// return createAESCipherSuite(16, TlsCipherSuite.KE_SRP_DSS); +// +// case TLS_SRP_SHA_WITH_AES_256_CBC_SHA: +// return createAESCipherSuite(32, TlsCipherSuite.KE_SRP); +// +// case TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA: +// return createAESCipherSuite(32, TlsCipherSuite.KE_SRP_RSA); +// +// case TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA: +// return createAESCipherSuite(32, TlsCipherSuite.KE_SRP_DSS); + + default: + handler.failWithError(TlsProtocolHandler.AL_fatal, TlsProtocolHandler.AP_handshake_failure); + + /* + * Unreachable Code, failWithError will always throw an exception! + */ + return null; + } + } + + private static TlsCipherSuite createAESCipherSuite(int cipherKeySize, short keyExchange) + { + return new TlsBlockCipherCipherSuite(createAESCipher(), createAESCipher(), + new SHA1Digest(), new SHA1Digest(), cipherKeySize, keyExchange); + } + + private static TlsCipherSuite createDESedeCipherSuite(int cipherKeySize, short keyExchange) + { + return new TlsBlockCipherCipherSuite(createDESedeCipher(), createDESedeCipher(), + new SHA1Digest(), new SHA1Digest(), cipherKeySize, keyExchange); + } + + private static CBCBlockCipher createAESCipher() + { + return new CBCBlockCipher(new AESFastEngine()); + } + + private static CBCBlockCipher createDESedeCipher() + { + return new CBCBlockCipher(new DESedeEngine()); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsDSSSigner.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsDSSSigner.java new file mode 100644 index 000000000..be33ffcab --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsDSSSigner.java @@ -0,0 +1,14 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +import com.google.bitcoin.bouncycastle.crypto.digests.SHA1Digest; +import com.google.bitcoin.bouncycastle.crypto.signers.DSADigestSigner; +import com.google.bitcoin.bouncycastle.crypto.signers.DSASigner; + +class TlsDSSSigner + extends DSADigestSigner +{ + TlsDSSSigner() + { + super(new DSASigner(), new SHA1Digest()); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsInputStream.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsInputStream.java new file mode 100644 index 000000000..61ef5368b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsInputStream.java @@ -0,0 +1,41 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.InputStream; + +/** + * An InputStream for an TLS 1.0 connection. + */ +public class TlsInputStream + extends InputStream +{ + private byte[] buf = new byte[1]; + private TlsProtocolHandler handler = null; + + TlsInputStream (TlsProtocolHandler handler) + { + this.handler = handler; + } + + public int read(byte[] buf, int offset, int len) + throws IOException + { + return this.handler.readApplicationData(buf, offset, len); + } + + public int read() + throws IOException + { + if (this.read(buf) < 0) + { + return -1; + } + return buf[0] & 0xff; + } + + public void close() + throws IOException + { + handler.close(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsMac.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsMac.java new file mode 100644 index 000000000..d6aac0f4d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsMac.java @@ -0,0 +1,78 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.macs.HMac; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * A generic TLS MAC implementation, which can be used with any kind of + * Digest to act as an HMAC. + */ +public class TlsMac +{ + private long seqNo; + private HMac mac; + + /** + * Generate a new instance of an TlsMac. + * + * @param digest The digest to use. + * @param key_block A byte-array where the key for this mac is located. + * @param offset The number of bytes to skip, before the key starts in the buffer. + * @param len The length of the key. + */ + protected TlsMac(Digest digest, byte[] key_block, int offset, int len) + { + this.mac = new HMac(digest); + KeyParameter param = new KeyParameter(key_block, offset, len); + this.mac.init(param); + this.seqNo = 0; + } + + /** + * @return The Keysize of the mac. + */ + protected int getSize() + { + return mac.getMacSize(); + } + + /** + * Calculate the mac for some given data. + *

    + * TlsMac will keep track of the sequence number internally. + * + * @param type The message type of the message. + * @param message A byte-buffer containing the message. + * @param offset The number of bytes to skip, before the message starts. + * @param len The length of the message. + * @return A new byte-buffer containing the mac value. + */ + protected byte[] calculateMac(short type, byte[] message, int offset, int len) + { + try + { + ByteArrayOutputStream bosMac = new ByteArrayOutputStream(); + TlsUtils.writeUint64(seqNo++, bosMac); + TlsUtils.writeUint8(type, bosMac); + TlsUtils.writeVersion(bosMac); + TlsUtils.writeUint16(len, bosMac); + bosMac.write(message, offset, len); + byte[] macData = bosMac.toByteArray(); + mac.update(macData, 0, macData.length); + byte[] result = new byte[mac.getMacSize()]; + mac.doFinal(result, 0); + mac.reset(); + return result; + } + catch (IOException e) + { + // This should never happen + throw new IllegalStateException("Internal error during mac calculation"); + } + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsNullCipherSuite.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsNullCipherSuite.java new file mode 100644 index 000000000..aff6b968d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsNullCipherSuite.java @@ -0,0 +1,33 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +/** + * A NULL CipherSuite in java, this should only be used during handshake. + */ +public class TlsNullCipherSuite extends TlsCipherSuite +{ + + protected void init(byte[] ms, byte[] cr, byte[] sr) + { + throw new TlsRuntimeException("Sorry, init of TLS_NULL_WITH_NULL_NULL is forbidden"); + } + + protected byte[] encodePlaintext(short type, byte[] plaintext, int offset, int len) + { + byte[] result = new byte[len]; + System.arraycopy(plaintext, offset, result, 0, len); + return result; + } + + protected byte[] decodeCiphertext(short type, byte[] plaintext, int offset, int len, TlsProtocolHandler handler) + { + byte[] result = new byte[len]; + System.arraycopy(plaintext, offset, result, 0, len); + return result; + } + + protected short getKeyExchangeAlgorithm() + { + return 0; + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsOuputStream.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsOuputStream.java new file mode 100644 index 000000000..1033e61e9 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsOuputStream.java @@ -0,0 +1,45 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * An OutputStream for an TLS connection. + */ +public class TlsOuputStream extends OutputStream +{ + private byte[] buf = new byte[1]; + private TlsProtocolHandler handler; + + TlsOuputStream(TlsProtocolHandler handler) + { + this.handler = handler; + } + + public void write(byte buf[], int offset, int len) throws IOException + { + this.handler.writeData(buf, offset, len); + } + + public void write(int arg0) throws IOException + { + buf[0] = (byte)arg0; + this.write(buf, 0, 1); + } + + /** @deprecated Use 'close' instead */ + public void cose() throws IOException + { + handler.close(); + } + + public void close() throws IOException + { + handler.close(); + } + + public void flush() throws IOException + { + handler.flush(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsProtocolHandler.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsProtocolHandler.java new file mode 100644 index 000000000..d73750e04 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsProtocolHandler.java @@ -0,0 +1,1407 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.Enumeration; +import java.util.Hashtable; + +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.x509.KeyUsage; +import com.google.bitcoin.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import com.google.bitcoin.bouncycastle.asn1.x509.X509CertificateStructure; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Extension; +import com.google.bitcoin.bouncycastle.asn1.x509.X509Extensions; +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPair; +import com.google.bitcoin.bouncycastle.crypto.CryptoException; +import com.google.bitcoin.bouncycastle.crypto.InvalidCipherTextException; +import com.google.bitcoin.bouncycastle.crypto.Signer; +import com.google.bitcoin.bouncycastle.crypto.agreement.DHBasicAgreement; +import com.google.bitcoin.bouncycastle.crypto.agreement.srp.SRP6Client; +import com.google.bitcoin.bouncycastle.crypto.digests.SHA1Digest; +import com.google.bitcoin.bouncycastle.crypto.encodings.PKCS1Encoding; +import com.google.bitcoin.bouncycastle.crypto.engines.RSABlindedEngine; +import com.google.bitcoin.bouncycastle.crypto.generators.DHBasicKeyPairGenerator; +import com.google.bitcoin.bouncycastle.crypto.io.SignerInputStream; +import com.google.bitcoin.bouncycastle.crypto.params.AsymmetricKeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.DHKeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHPublicKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DSAPublicKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ParametersWithRandom; +import com.google.bitcoin.bouncycastle.crypto.params.RSAKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.prng.ThreadedSeedGenerator; +import com.google.bitcoin.bouncycastle.crypto.util.PublicKeyFactory; +import com.google.bitcoin.bouncycastle.util.BigIntegers; + +/** + * An implementation of all high level protocols in TLS 1.0. + */ +public class TlsProtocolHandler +{ + private static final BigInteger ONE = BigInteger.valueOf(1); + private static final BigInteger TWO = BigInteger.valueOf(2); + + private static final short RL_CHANGE_CIPHER_SPEC = 20; + + private static final short RL_ALERT = 21; + + private static final short RL_HANDSHAKE = 22; + + private static final short RL_APPLICATION_DATA = 23; + + /* + hello_request(0), client_hello(1), server_hello(2), + certificate(11), server_key_exchange (12), + certificate_request(13), server_hello_done(14), + certificate_verify(15), client_key_exchange(16), + finished(20), (255) + */ + + private static final short HP_HELLO_REQUEST = 0; + + private static final short HP_CLIENT_HELLO = 1; + + private static final short HP_SERVER_HELLO = 2; + + private static final short HP_CERTIFICATE = 11; + + private static final short HP_SERVER_KEY_EXCHANGE = 12; + + private static final short HP_CERTIFICATE_REQUEST = 13; + + private static final short HP_SERVER_HELLO_DONE = 14; + + private static final short HP_CERTIFICATE_VERIFY = 15; + + private static final short HP_CLIENT_KEY_EXCHANGE = 16; + + private static final short HP_FINISHED = 20; + + /* + * Our Connection states + */ + + private static final short CS_CLIENT_HELLO_SEND = 1; + + private static final short CS_SERVER_HELLO_RECEIVED = 2; + + private static final short CS_SERVER_CERTIFICATE_RECEIVED = 3; + + private static final short CS_SERVER_KEY_EXCHANGE_RECEIVED = 4; + + private static final short CS_CERTIFICATE_REQUEST_RECEIVED = 5; + + private static final short CS_SERVER_HELLO_DONE_RECEIVED = 6; + + private static final short CS_CLIENT_KEY_EXCHANGE_SEND = 7; + + private static final short CS_CLIENT_VERIFICATION_SEND = 8; + + private static final short CS_CLIENT_CHANGE_CIPHER_SPEC_SEND = 9; + + private static final short CS_CLIENT_FINISHED_SEND = 10; + + private static final short CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED = 11; + + private static final short CS_DONE = 12; + + + protected static final short AP_close_notify = 0; + protected static final short AP_unexpected_message = 10; + protected static final short AP_bad_record_mac = 20; + protected static final short AP_decryption_failed = 21; + protected static final short AP_record_overflow = 22; + protected static final short AP_decompression_failure = 30; + protected static final short AP_handshake_failure = 40; + protected static final short AP_bad_certificate = 42; + protected static final short AP_unsupported_certificate = 43; + protected static final short AP_certificate_revoked = 44; + protected static final short AP_certificate_expired = 45; + protected static final short AP_certificate_unknown = 46; + protected static final short AP_illegal_parameter = 47; + protected static final short AP_unknown_ca = 48; + protected static final short AP_access_denied = 49; + protected static final short AP_decode_error = 50; + protected static final short AP_decrypt_error = 51; + protected static final short AP_export_restriction = 60; + protected static final short AP_protocol_version = 70; + protected static final short AP_insufficient_security = 71; + protected static final short AP_internal_error = 80; + protected static final short AP_user_canceled = 90; + protected static final short AP_no_renegotiation = 100; + + protected static final short AL_warning = 1; + protected static final short AL_fatal = 2; + + private static final byte[] emptybuf = new byte[0]; + + private static final String TLS_ERROR_MESSAGE = "Internal TLS error, this could be an attack"; + + /* + * Queues for data from some protocols. + */ + + private ByteQueue applicationDataQueue = new ByteQueue(); + + private ByteQueue changeCipherSpecQueue = new ByteQueue(); + + private ByteQueue alertQueue = new ByteQueue(); + + private ByteQueue handshakeQueue = new ByteQueue(); + + /* + * The Record Stream we use + */ + + private RecordStream rs; + + private SecureRandom random; + + /* + * The public key of the server. + */ + + private AsymmetricKeyParameter serverPublicKey = null; + + private TlsInputStream tlsInputStream = null; + private TlsOuputStream tlsOutputStream = null; + + private boolean closed = false; + private boolean failedWithError = false; + private boolean appDataReady = false; + private boolean extendedClientHello; + + private byte[] clientRandom; + private byte[] serverRandom; + private byte[] ms; + + private TlsCipherSuite chosenCipherSuite = null; + + private BigInteger SRP_A; + private byte[] SRP_identity, SRP_password; + private BigInteger Yc; + private byte[] pms; + + private CertificateVerifyer verifyer = null; + + public TlsProtocolHandler(InputStream is, OutputStream os) + { + /* + * We use our threaded seed generator to generate a good random + * seed. If the user has a better random seed, he should use + * the constructor with a SecureRandom. + */ + ThreadedSeedGenerator tsg = new ThreadedSeedGenerator(); + this.random = new SecureRandom(); + /* + * Hopefully, 20 bytes in fast mode are good enough. + */ + this.random.setSeed(tsg.generateSeed(20, true)); + + this.rs = new RecordStream(this, is, os); + } + + public TlsProtocolHandler(InputStream is, OutputStream os, SecureRandom sr) + { + this.random = sr; + this.rs = new RecordStream(this, is, os); + } + + private short connection_state; + + protected void processData(short protocol, byte[] buf, int offset, int len) + throws IOException + { + /* + * Have a look at the protocol type, and add it to the correct queue. + */ + switch (protocol) + { + case RL_CHANGE_CIPHER_SPEC: + changeCipherSpecQueue.addData(buf, offset, len); + processChangeCipherSpec(); + break; + case RL_ALERT: + alertQueue.addData(buf, offset, len); + processAlert(); + break; + case RL_HANDSHAKE: + handshakeQueue.addData(buf, offset, len); + processHandshake(); + break; + case RL_APPLICATION_DATA: + if (!appDataReady) + { + this.failWithError(AL_fatal, AP_unexpected_message); + } + applicationDataQueue.addData(buf, offset, len); + processApplicationData(); + break; + default: + /* + * Uh, we don't know this protocol. + * + * RFC2246 defines on page 13, that we should ignore this. + */ + + } + } + + private void processHandshake() throws IOException + { + boolean read; + do + { + read = false; + + /* + * We need the first 4 bytes, they contain type and length of + * the message. + */ + if (handshakeQueue.size() >= 4) + { + byte[] beginning = new byte[4]; + handshakeQueue.read(beginning, 0, 4, 0); + ByteArrayInputStream bis = new ByteArrayInputStream(beginning); + short type = TlsUtils.readUint8(bis); + int len = TlsUtils.readUint24(bis); + + /* + * Check if we have enough bytes in the buffer to read + * the full message. + */ + if (handshakeQueue.size() >= (len + 4)) + { + /* + * Read the message. + */ + byte[] buf = new byte[len]; + handshakeQueue.read(buf, 0, len, 4); + handshakeQueue.removeData(len + 4); + + /* + * If it is not a finished message, update our hashes + * we prepare for the finish message. + */ + if (type != HP_FINISHED) + { + rs.hash1.update(beginning, 0, 4); + rs.hash2.update(beginning, 0, 4); + rs.hash1.update(buf, 0, len); + rs.hash2.update(buf, 0, len); + } + + /* + * Now, parse the message. + */ + ByteArrayInputStream is = new ByteArrayInputStream(buf); + + /* + * Check the type. + */ + switch (type) + { + case HP_CERTIFICATE: + { + switch (connection_state) + { + case CS_SERVER_HELLO_RECEIVED: + { + /* + * Parse the certificates. + */ + Certificate cert = Certificate.parse(is); + assertEmpty(is); + + X509CertificateStructure x509Cert = cert.certs[0]; + SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo(); + + try + { + this.serverPublicKey = PublicKeyFactory.createKey(keyInfo); + } + catch (RuntimeException e) + { + this.failWithError(AL_fatal, AP_unsupported_certificate); + } + + // Sanity check the PublicKeyFactory + if (this.serverPublicKey.isPrivate()) + { + this.failWithError(AL_fatal, AP_internal_error); + } + + /* + * Perform various checks per RFC2246 7.4.2 + * TODO "Unless otherwise specified, the signing algorithm for the certificate + * must be the same as the algorithm for the certificate key." + */ + switch (this.chosenCipherSuite.getKeyExchangeAlgorithm()) + { + case TlsCipherSuite.KE_RSA: + if (!(this.serverPublicKey instanceof RSAKeyParameters)) + { + this.failWithError(AL_fatal, AP_certificate_unknown); + } + validateKeyUsage(x509Cert, KeyUsage.keyEncipherment); + break; + case TlsCipherSuite.KE_DHE_RSA: + case TlsCipherSuite.KE_SRP_RSA: + if (!(this.serverPublicKey instanceof RSAKeyParameters)) + { + this.failWithError(AL_fatal, AP_certificate_unknown); + } + validateKeyUsage(x509Cert, KeyUsage.digitalSignature); + break; + case TlsCipherSuite.KE_DHE_DSS: + case TlsCipherSuite.KE_SRP_DSS: + if (!(this.serverPublicKey instanceof DSAPublicKeyParameters)) + { + this.failWithError(AL_fatal, AP_certificate_unknown); + } + break; + default: + this.failWithError(AL_fatal, AP_unsupported_certificate); + } + + /* + * Verify them. + */ + if (!this.verifyer.isValid(cert.getCerts())) + { + this.failWithError(AL_fatal, AP_user_canceled); + } + + break; + } + default: + this.failWithError(AL_fatal, AP_unexpected_message); + } + + connection_state = CS_SERVER_CERTIFICATE_RECEIVED; + read = true; + break; + } + case HP_FINISHED: + switch (connection_state) + { + case CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED: + /* + * Read the checksum from the finished message, + * it has always 12 bytes. + */ + byte[] receivedChecksum = new byte[12]; + TlsUtils.readFully(receivedChecksum, is); + assertEmpty(is); + + /* + * Calculate our own checksum. + */ + byte[] checksum = new byte[12]; + byte[] md5andsha1 = new byte[16 + 20]; + rs.hash2.doFinal(md5andsha1, 0); + TlsUtils.PRF(this.ms, TlsUtils.toByteArray("server finished"), md5andsha1, checksum); + + /* + * Compare both checksums. + */ + for (int i = 0; i < receivedChecksum.length; i++) + { + if (receivedChecksum[i] != checksum[i]) + { + /* + * Wrong checksum in the finished message. + */ + this.failWithError(AL_fatal, AP_handshake_failure); + } + } + + connection_state = CS_DONE; + + /* + * We are now ready to receive application data. + */ + this.appDataReady = true; + read = true; + break; + default: + this.failWithError(AL_fatal, AP_unexpected_message); + } + break; + case HP_SERVER_HELLO: + switch (connection_state) + { + case CS_CLIENT_HELLO_SEND: + /* + * Read the server hello message + */ + TlsUtils.checkVersion(is, this); + + /* + * Read the server random + */ + this.serverRandom = new byte[32]; + TlsUtils.readFully(this.serverRandom, is); + + /* + * Currently, we don't support session ids + */ + byte[] sessionId = TlsUtils.readOpaque8(is); + + /* + * Find out which ciphersuite the server has + * chosen. If we don't support this ciphersuite, + * the TlsCipherSuiteManager will throw an + * exception. + */ + this.chosenCipherSuite = TlsCipherSuiteManager.getCipherSuite(TlsUtils.readUint16(is), this); + + /* + * We support only the null compression which + * means no compression. + */ + short compressionMethod = TlsUtils.readUint8(is); + if (compressionMethod != 0) + { + this.failWithError(TlsProtocolHandler.AL_fatal, TlsProtocolHandler.AP_illegal_parameter); + } + + /* + * RFC4366 2.2 + * The extended server hello message format MAY be sent + * in place of the server hello message when the client + * has requested extended functionality via the extended + * client hello message specified in Section 2.1. + */ + if (extendedClientHello && is.available() > 0) + { + // Process extensions from extended server hello + byte[] extBytes = TlsUtils.readOpaque16(is); + + // Integer -> byte[] + Hashtable serverExtensions = new Hashtable(); + + ByteArrayInputStream ext = new ByteArrayInputStream(extBytes); + while (ext.available() > 0) + { + int extType = TlsUtils.readUint16(ext); + byte[] extValue = TlsUtils.readOpaque16(ext); + + serverExtensions.put(new Integer(extType), extValue); + } + + // TODO Validate/process serverExtensions (via client?) + // TODO[SRP] + } + + assertEmpty(is); + + connection_state = CS_SERVER_HELLO_RECEIVED; + read = true; + break; + default: + this.failWithError(AL_fatal, AP_unexpected_message); + } + break; + case HP_SERVER_HELLO_DONE: + switch (connection_state) + { + + case CS_SERVER_CERTIFICATE_RECEIVED: + /* + * There was no server key exchange message, check + * that we are doing RSA key exchange. + */ + if (this.chosenCipherSuite.getKeyExchangeAlgorithm() != TlsCipherSuite.KE_RSA) + { + this.failWithError(AL_fatal, AP_unexpected_message); + } + + /* + * NB: Fall through to next case label to continue RSA key exchange + */ + + case CS_SERVER_KEY_EXCHANGE_RECEIVED: + case CS_CERTIFICATE_REQUEST_RECEIVED: + + assertEmpty(is); + boolean isCertReq = (connection_state == CS_CERTIFICATE_REQUEST_RECEIVED); + connection_state = CS_SERVER_HELLO_DONE_RECEIVED; + + if (isCertReq) + { + sendClientCertificate(); + } + + /* + * Send the client key exchange message, depending + * on the key exchange we are using in our + * ciphersuite. + */ + switch (this.chosenCipherSuite.getKeyExchangeAlgorithm()) + { + case TlsCipherSuite.KE_RSA: + { + /* + * We are doing RSA key exchange. We will + * choose a pre master secret and send it + * rsa encrypted to the server. + * + * Prepare pre master secret. + */ + pms = new byte[48]; + random.nextBytes(pms); + pms[0] = 3; + pms[1] = 1; + + /* + * Encode the pms and send it to the server. + * + * Prepare an PKCS1Encoding with good random + * padding. + */ + RSABlindedEngine rsa = new RSABlindedEngine(); + PKCS1Encoding encoding = new PKCS1Encoding(rsa); + encoding.init(true, new ParametersWithRandom(this.serverPublicKey, this.random)); + byte[] encrypted = null; + try + { + encrypted = encoding.processBlock(pms, 0, pms.length); + } + catch (InvalidCipherTextException e) + { + /* + * This should never happen, only during decryption. + */ + this.failWithError(AL_fatal, AP_internal_error); + } + + /* + * Send the encrypted pms. + */ + sendClientKeyExchange(encrypted); + break; + } + case TlsCipherSuite.KE_DHE_DSS: + case TlsCipherSuite.KE_DHE_RSA: + { + /* + * Send the Client Key Exchange message for + * DHE key exchange. + */ + byte[] YcByte = BigIntegers.asUnsignedByteArray(this.Yc); + + sendClientKeyExchange(YcByte); + + break; + } + case TlsCipherSuite.KE_SRP: + case TlsCipherSuite.KE_SRP_RSA: + case TlsCipherSuite.KE_SRP_DSS: + { + /* + * Send the Client Key Exchange message for + * SRP key exchange. + */ + byte[] bytes = BigIntegers.asUnsignedByteArray(this.SRP_A); + + sendClientKeyExchange(bytes); + + break; + } + default: + /* + * Problem during handshake, we don't know + * how to handle this key exchange method. + */ + this.failWithError(AL_fatal, AP_unexpected_message); + + } + + connection_state = CS_CLIENT_KEY_EXCHANGE_SEND; + + /* + * Now, we send change cipher state + */ + byte[] cmessage = new byte[1]; + cmessage[0] = 1; + rs.writeMessage((short)RL_CHANGE_CIPHER_SPEC, cmessage, 0, cmessage.length); + + connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC_SEND; + + /* + * Calculate the ms + */ + this.ms = new byte[48]; + byte[] random = new byte[clientRandom.length + serverRandom.length]; + System.arraycopy(clientRandom, 0, random, 0, clientRandom.length); + System.arraycopy(serverRandom, 0, random, clientRandom.length, serverRandom.length); + TlsUtils.PRF(pms, TlsUtils.toByteArray("master secret"), random, this.ms); + + /* + * Initialize our cipher suite + */ + rs.writeSuite = this.chosenCipherSuite; + rs.writeSuite.init(this.ms, clientRandom, serverRandom); + + /* + * Send our finished message. + */ + byte[] checksum = new byte[12]; + byte[] md5andsha1 = new byte[16 + 20]; + rs.hash1.doFinal(md5andsha1, 0); + TlsUtils.PRF(this.ms, TlsUtils.toByteArray("client finished"), md5andsha1, checksum); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + TlsUtils.writeUint8(HP_FINISHED, bos); + TlsUtils.writeUint24(12, bos); + bos.write(checksum); + byte[] message = bos.toByteArray(); + + rs.writeMessage((short)RL_HANDSHAKE, message, 0, message.length); + + this.connection_state = CS_CLIENT_FINISHED_SEND; + read = true; + break; + default: + this.failWithError(AL_fatal, AP_handshake_failure); + } + break; + case HP_SERVER_KEY_EXCHANGE: + { + switch (connection_state) + { + case CS_SERVER_HELLO_RECEIVED: + /* + * Check that we are doing SRP key exchange + */ + if (this.chosenCipherSuite.getKeyExchangeAlgorithm() != TlsCipherSuite.KE_SRP) + { + this.failWithError(AL_fatal, AP_unexpected_message); + } + + // NB: Fall through to next case label + + case CS_SERVER_CERTIFICATE_RECEIVED: + { + /* + * Check that we are doing DHE key exchange + */ + switch (this.chosenCipherSuite.getKeyExchangeAlgorithm()) + { + case TlsCipherSuite.KE_DHE_RSA: + { + processDHEKeyExchange(is, new TlsRSASigner()); + break; + } + case TlsCipherSuite.KE_DHE_DSS: + { + processDHEKeyExchange(is, new TlsDSSSigner()); + break; + } + case TlsCipherSuite.KE_SRP: + { + processSRPKeyExchange(is, null); + break; + } + case TlsCipherSuite.KE_SRP_RSA: + { + processSRPKeyExchange(is, new TlsRSASigner()); + break; + } + case TlsCipherSuite.KE_SRP_DSS: + { + processSRPKeyExchange(is, new TlsDSSSigner()); + break; + } + default: + this.failWithError(AL_fatal, AP_unexpected_message); + } + break; + } + default: + this.failWithError(AL_fatal, AP_unexpected_message); + } + + this.connection_state = CS_SERVER_KEY_EXCHANGE_RECEIVED; + read = true; + break; + } + case HP_CERTIFICATE_REQUEST: + { + switch (connection_state) + { + case CS_SERVER_CERTIFICATE_RECEIVED: + /* + * There was no server key exchange message, check + * that we are doing RSA key exchange. + */ + if (this.chosenCipherSuite.getKeyExchangeAlgorithm() != TlsCipherSuite.KE_RSA) + { + this.failWithError(AL_fatal, AP_unexpected_message); + } + + /* + * NB: Fall through to next case label to continue RSA key exchange + */ + + case CS_SERVER_KEY_EXCHANGE_RECEIVED: + { + byte[] types = TlsUtils.readOpaque8(is); + byte[] auths = TlsUtils.readOpaque8(is); + + // TODO Validate/process + + assertEmpty(is); + break; + } + default: + this.failWithError(AL_fatal, AP_unexpected_message); + } + + this.connection_state = CS_CERTIFICATE_REQUEST_RECEIVED; + read = true; + break; + } + case HP_HELLO_REQUEST: + case HP_CLIENT_KEY_EXCHANGE: + case HP_CERTIFICATE_VERIFY: + case HP_CLIENT_HELLO: + default: + // We do not support this! + this.failWithError(AL_fatal, AP_unexpected_message); + break; + } + } + } + } + while (read); + + } + + private void processApplicationData() + { + /* + * There is nothing we need to do here. + * + * This function could be used for callbacks when application + * data arrives in the future. + */ + } + + private void processAlert() throws IOException + { + while (alertQueue.size() >= 2) + { + /* + * An alert is always 2 bytes. Read the alert. + */ + byte[] tmp = new byte[2]; + alertQueue.read(tmp, 0, 2, 0); + alertQueue.removeData(2); + short level = tmp[0]; + short description = tmp[1]; + if (level == AL_fatal) + { + /* + * This is a fatal error. + */ + this.failedWithError = true; + this.closed = true; + /* + * Now try to close the stream, ignore errors. + */ + try + { + rs.close(); + } + catch (Exception e) + { + + } + throw new IOException(TLS_ERROR_MESSAGE); + } + else + { + /* + * This is just a warning. + */ + if (description == AP_close_notify) + { + /* + * Close notify + */ + this.failWithError(AL_warning, AP_close_notify); + } + /* + * If it is just a warning, we continue. + */ + } + } + } + + /** + * This method is called, when a change cipher spec message is received. + * + * @throws IOException If the message has an invalid content or the + * handshake is not in the correct state. + */ + private void processChangeCipherSpec() throws IOException + { + while (changeCipherSpecQueue.size() > 0) + { + /* + * A change cipher spec message is only one byte with the value 1. + */ + byte[] b = new byte[1]; + changeCipherSpecQueue.read(b, 0, 1, 0); + changeCipherSpecQueue.removeData(1); + if (b[0] != 1) + { + /* + * This should never happen. + */ + this.failWithError(AL_fatal, AP_unexpected_message); + + } + else + { + /* + * Check if we are in the correct connection state. + */ + if (this.connection_state == CS_CLIENT_FINISHED_SEND) + { + rs.readSuite = rs.writeSuite; + this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED; + } + else + { + /* + * We are not in the correct connection state. + */ + this.failWithError(AL_fatal, AP_handshake_failure); + } + + } + } + } + + private void processDHEKeyExchange(ByteArrayInputStream is, Signer signer) + throws IOException + { + InputStream sigIn = is; + if (signer != null) + { + signer.init(false, this.serverPublicKey); + signer.update(this.clientRandom, 0, this.clientRandom.length); + signer.update(this.serverRandom, 0, this.serverRandom.length); + + sigIn = new SignerInputStream(is, signer); + } + + /* + * Parse the Structure + */ + byte[] pByte = TlsUtils.readOpaque16(sigIn); + byte[] gByte = TlsUtils.readOpaque16(sigIn); + byte[] YsByte = TlsUtils.readOpaque16(sigIn); + + if (signer != null) + { + byte[] sigByte = TlsUtils.readOpaque16(is); + + /* + * Verify the Signature. + */ + if (!signer.verifySignature(sigByte)) + { + this.failWithError(AL_fatal, AP_bad_certificate); + } + } + + this.assertEmpty(is); + + /* + * Do the DH calculation. + */ + BigInteger p = new BigInteger(1, pByte); + BigInteger g = new BigInteger(1, gByte); + BigInteger Ys = new BigInteger(1, YsByte); + + /* + * Check the DH parameter values + */ + if (!p.isProbablePrime(10)) + { + this.failWithError(AL_fatal, AP_illegal_parameter); + } + if (g.compareTo(TWO) < 0 || g.compareTo(p.subtract(TWO)) > 0) + { + this.failWithError(AL_fatal, AP_illegal_parameter); + } + // TODO For static DH public values, see additional checks in RFC 2631 2.1.5 + if (Ys.compareTo(TWO) < 0 || Ys.compareTo(p.subtract(ONE)) > 0) + { + this.failWithError(AL_fatal, AP_illegal_parameter); + } + + /* + * Diffie-Hellman basic key agreement + */ + DHParameters dhParams = new DHParameters(p, g); + + // Generate a keypair + DHBasicKeyPairGenerator dhGen = new DHBasicKeyPairGenerator(); + dhGen.init(new DHKeyGenerationParameters(random, dhParams)); + + AsymmetricCipherKeyPair dhPair = dhGen.generateKeyPair(); + + // Store the public value to send to server + this.Yc = ((DHPublicKeyParameters)dhPair.getPublic()).getY(); + + // Calculate the shared secret + DHBasicAgreement dhAgree = new DHBasicAgreement(); + dhAgree.init(dhPair.getPrivate()); + + BigInteger agreement = dhAgree.calculateAgreement(new DHPublicKeyParameters(Ys, dhParams)); + + this.pms = BigIntegers.asUnsignedByteArray(agreement); + } + + private void processSRPKeyExchange(ByteArrayInputStream is, Signer signer) + throws IOException + { + InputStream sigIn = is; + if (signer != null) + { + signer.init(false, this.serverPublicKey); + signer.update(this.clientRandom, 0, this.clientRandom.length); + signer.update(this.serverRandom, 0, this.serverRandom.length); + + sigIn = new SignerInputStream(is, signer); + } + + /* + * Parse the Structure + */ + byte[] NByte = TlsUtils.readOpaque16(sigIn); + byte[] gByte = TlsUtils.readOpaque16(sigIn); + byte[] sByte = TlsUtils.readOpaque8(sigIn); + byte[] BByte = TlsUtils.readOpaque16(sigIn); + + if (signer != null) + { + byte[] sigByte = TlsUtils.readOpaque16(is); + + /* + * Verify the Signature. + */ + if (!signer.verifySignature(sigByte)) + { + this.failWithError(AL_fatal, AP_bad_certificate); + } + } + + this.assertEmpty(is); + + BigInteger N = new BigInteger(1, NByte); + BigInteger g = new BigInteger(1, gByte); + byte[] s = sByte; + BigInteger B = new BigInteger(1, BByte); + + SRP6Client srpClient = new SRP6Client(); + srpClient.init(N, g, new SHA1Digest(), random); + + this.SRP_A = srpClient.generateClientCredentials(s, this.SRP_identity, + this.SRP_password); + + try + { + BigInteger S = srpClient.calculateSecret(B); + this.pms = BigIntegers.asUnsignedByteArray(S); + } + catch (CryptoException e) + { + this.failWithError(AL_fatal, AP_illegal_parameter); + } + } + + private void validateKeyUsage(X509CertificateStructure c, int keyUsageBits) + throws IOException + { + X509Extensions exts = c.getTBSCertificate().getExtensions(); + if (exts != null) + { + X509Extension ext = exts.getExtension(X509Extensions.KeyUsage); + if (ext != null) + { + DERBitString ku = KeyUsage.getInstance(ext); + int bits = ku.getBytes()[0] & 0xff; + if ((bits & keyUsageBits) != keyUsageBits) + { + this.failWithError(AL_fatal, AP_certificate_unknown); + } + } + } + } + + private void sendClientCertificate() throws IOException + { + /* + * just write back the "no client certificate" message + * see also gnutls, auth_cert.c:643 (0B 00 00 03 00 00 00) + */ + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + TlsUtils.writeUint8(HP_CERTIFICATE, bos); + TlsUtils.writeUint24(3, bos); + TlsUtils.writeUint24(0, bos); + byte[] message = bos.toByteArray(); + + rs.writeMessage((short)RL_HANDSHAKE, message, 0, message.length); + } + + private void sendClientKeyExchange(byte[] keData) throws IOException + { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + TlsUtils.writeUint8(HP_CLIENT_KEY_EXCHANGE, bos); + TlsUtils.writeUint24(keData.length + 2, bos); + TlsUtils.writeOpaque16(keData, bos); + byte[] message = bos.toByteArray(); + + rs.writeMessage((short)RL_HANDSHAKE, message, 0, message.length); + } + + /** + * Connects to the remote system. + * + * @param verifyer Will be used when a certificate is received to verify + * that this certificate is accepted by the client. + * @throws IOException If handshake was not successful. + */ + public void connect(CertificateVerifyer verifyer) throws IOException + { + this.verifyer = verifyer; + + /* + * Send Client hello + * + * First, generate some random data. + */ + this.clientRandom = new byte[32]; + random.nextBytes(this.clientRandom); + + int t = (int)(System.currentTimeMillis() / 1000); + this.clientRandom[0] = (byte)(t >> 24); + this.clientRandom[1] = (byte)(t >> 16); + this.clientRandom[2] = (byte)(t >> 8); + this.clientRandom[3] = (byte)t; + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + TlsUtils.writeVersion(os); + os.write(this.clientRandom); + + /* + * Length of Session id + */ + TlsUtils.writeUint8((short)0, os); + + /* + * Cipher suites + */ + TlsCipherSuiteManager.writeCipherSuites(os); + + /* + * Compression methods, just the null method. + */ + byte[] compressionMethods = new byte[]{0x00}; + TlsUtils.writeOpaque8(compressionMethods, os); + + /* + * Extensions + */ + // TODO Collect extensions from client + // Integer -> byte[] + Hashtable clientExtensions = new Hashtable(); + + // TODO[SRP] +// { +// ByteArrayOutputStream srpData = new ByteArrayOutputStream(); +// TlsUtils.writeOpaque8(SRP_identity, srpData); +// +// // TODO[SRP] RFC5054 2.8.1: ExtensionType.srp = 12 +// clientExtensions.put(Integer.valueOf(12), srpData.toByteArray()); +// } + + this.extendedClientHello = !clientExtensions.isEmpty(); + + if (extendedClientHello) + { + ByteArrayOutputStream ext = new ByteArrayOutputStream(); + + Enumeration keys = clientExtensions.keys(); + while (keys.hasMoreElements()) + { + Integer extType = (Integer)keys.nextElement(); + byte[] extValue = (byte[])clientExtensions.get(extType); + + TlsUtils.writeUint16(extType.intValue(), ext); + TlsUtils.writeOpaque16(extValue, ext); + } + + TlsUtils.writeOpaque16(ext.toByteArray(), os); + } + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + TlsUtils.writeUint8(HP_CLIENT_HELLO, bos); + TlsUtils.writeUint24(os.size(), bos); + bos.write(os.toByteArray()); + byte[] message = bos.toByteArray(); + rs.writeMessage(RL_HANDSHAKE, message, 0, message.length); + connection_state = CS_CLIENT_HELLO_SEND; + + /* + * We will now read data, until we have completed the handshake. + */ + while (connection_state != CS_DONE) + { + rs.readData(); + } + + this.tlsInputStream = new TlsInputStream(this); + this.tlsOutputStream = new TlsOuputStream(this); + } + + /** + * Read data from the network. The method will return immediately, if there is + * still some data left in the buffer, or block until some application + * data has been read from the network. + * + * @param buf The buffer where the data will be copied to. + * @param offset The position where the data will be placed in the buffer. + * @param len The maximum number of bytes to read. + * @return The number of bytes read. + * @throws IOException If something goes wrong during reading data. + */ + protected int readApplicationData(byte[] buf, int offset, int len) throws IOException + { + while (applicationDataQueue.size() == 0) + { + /* + * We need to read some data. + */ + if (this.failedWithError) + { + /* + * Something went terribly wrong, we should throw an IOException + */ + throw new IOException(TLS_ERROR_MESSAGE); + } + if (this.closed) + { + /* + * Connection has been closed, there is no more data to read. + */ + return -1; + } + + try + { + rs.readData(); + } + catch (IOException e) + { + if (!this.closed) + { + this.failWithError(AL_fatal, AP_internal_error); + } + throw e; + } + catch (RuntimeException e) + { + if (!this.closed) + { + this.failWithError(AL_fatal, AP_internal_error); + } + throw e; + } + } + len = Math.min(len, applicationDataQueue.size()); + applicationDataQueue.read(buf, offset, len, 0); + applicationDataQueue.removeData(len); + return len; + } + + /** + * Send some application data to the remote system. + *

    + * The method will handle fragmentation internally. + * + * @param buf The buffer with the data. + * @param offset The position in the buffer where the data is placed. + * @param len The length of the data. + * @throws IOException If something goes wrong during sending. + */ + protected void writeData(byte[] buf, int offset, int len) throws IOException + { + if (this.failedWithError) + { + throw new IOException(TLS_ERROR_MESSAGE); + } + if (this.closed) + { + throw new IOException("Sorry, connection has been closed, you cannot write more data"); + } + + /* + * Protect against known IV attack! + * + * DO NOT REMOVE THIS LINE, EXCEPT YOU KNOW EXACTLY WHAT + * YOU ARE DOING HERE. + */ + rs.writeMessage(RL_APPLICATION_DATA, emptybuf, 0, 0); + + do + { + /* + * We are only allowed to write fragments up to 2^14 bytes. + */ + int toWrite = Math.min(len, 1 << 14); + + try + { + rs.writeMessage(RL_APPLICATION_DATA, buf, offset, toWrite); + } + catch (IOException e) + { + if (!closed) + { + this.failWithError(AL_fatal, AP_internal_error); + } + throw e; + } + catch (RuntimeException e) + { + if (!closed) + { + this.failWithError(AL_fatal, AP_internal_error); + } + throw e; + } + + + offset += toWrite; + len -= toWrite; + } + while (len > 0); + + } + + /** @deprecated use 'getOutputStream' instead */ + public TlsOuputStream getTlsOuputStream() + { + return this.tlsOutputStream; + } + + /** + * @return An OutputStream which can be used to send data. + */ + public OutputStream getOutputStream() + { + return this.tlsOutputStream; + } + + /** @deprecated use 'getInputStream' instead */ + public TlsInputStream getTlsInputStream() + { + return this.tlsInputStream; + } + + /** + * @return An InputStream which can be used to read data. + */ + public InputStream getInputStream() + { + return this.tlsInputStream; + } + + /** + * Terminate this connection with an alert. + *

    + * Can be used for normal closure too. + * + * @param alertLevel The level of the alert, an be AL_fatal or AL_warning. + * @param alertDescription The exact alert message. + * @throws IOException If alert was fatal. + */ + protected void failWithError(short alertLevel, short alertDescription) throws IOException + { + /* + * Check if the connection is still open. + */ + if (!closed) + { + /* + * Prepare the message + */ + byte[] error = new byte[2]; + error[0] = (byte)alertLevel; + error[1] = (byte)alertDescription; + this.closed = true; + + if (alertLevel == AL_fatal) + { + /* + * This is a fatal message. + */ + this.failedWithError = true; + } + rs.writeMessage(RL_ALERT, error, 0, 2); + rs.close(); + if (alertLevel == AL_fatal) + { + throw new IOException(TLS_ERROR_MESSAGE); + } + } + else + { + throw new IOException(TLS_ERROR_MESSAGE); + } + } + + /** + * Closes this connection. + * + * @throws IOException If something goes wrong during closing. + */ + public void close() throws IOException + { + if (!closed) + { + this.failWithError((short)1, (short)0); + } + } + + /** + * Make sure the InputStream is now empty. Fail otherwise. + * + * @param is The InputStream to check. + * @throws IOException If is is not empty. + */ + protected void assertEmpty(ByteArrayInputStream is) throws IOException + { + if (is.available() > 0) + { + this.failWithError(AL_fatal, AP_decode_error); + } + } + + protected void flush() throws IOException + { + rs.flush(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsRSASigner.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsRSASigner.java new file mode 100644 index 000000000..b769a79b8 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsRSASigner.java @@ -0,0 +1,14 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +import com.google.bitcoin.bouncycastle.crypto.encodings.PKCS1Encoding; +import com.google.bitcoin.bouncycastle.crypto.engines.RSABlindedEngine; +import com.google.bitcoin.bouncycastle.crypto.signers.GenericSigner; + +class TlsRSASigner + extends GenericSigner +{ + TlsRSASigner() + { + super(new PKCS1Encoding(new RSABlindedEngine()), new CombinedHash()); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsRuntimeException.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsRuntimeException.java new file mode 100644 index 000000000..878510304 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsRuntimeException.java @@ -0,0 +1,24 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +public class TlsRuntimeException + extends RuntimeException +{ + Throwable e; + + public TlsRuntimeException(String message, Throwable e) + { + super(message); + + this.e = e; + } + + public TlsRuntimeException(String message) + { + super(message); + } + + public Throwable getCause() + { + return e; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsUtils.java b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsUtils.java new file mode 100644 index 000000000..0867dacf3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/tls/TlsUtils.java @@ -0,0 +1,266 @@ +package com.google.bitcoin.bouncycastle.crypto.tls; + +import com.google.bitcoin.bouncycastle.crypto.Digest; +import com.google.bitcoin.bouncycastle.crypto.digests.MD5Digest; +import com.google.bitcoin.bouncycastle.crypto.digests.SHA1Digest; +import com.google.bitcoin.bouncycastle.crypto.macs.HMac; +import com.google.bitcoin.bouncycastle.crypto.params.KeyParameter; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Some helper fuctions for MicroTLS. + */ +public class TlsUtils +{ + static byte[] toByteArray(String str) + { + char[] chars = str.toCharArray(); + byte[] bytes = new byte[chars.length]; + + for (int i = 0; i != bytes.length; i++) + { + bytes[i] = (byte)chars[i]; + } + + return bytes; + } + + protected static void writeUint8(short i, OutputStream os) throws IOException + { + os.write(i); + } + + protected static void writeUint8(short i, byte[] buf, int offset) + { + buf[offset] = (byte)i; + } + + protected static void writeUint16(int i, OutputStream os) throws IOException + { + os.write(i >> 8); + os.write(i); + } + + protected static void writeUint16(int i, byte[] buf, int offset) + { + buf[offset] = (byte)(i >> 8); + buf[offset + 1] = (byte)i; + } + + protected static void writeUint24(int i, OutputStream os) throws IOException + { + os.write(i >> 16); + os.write(i >> 8); + os.write(i); + } + + protected static void writeUint24(int i, byte[] buf, int offset) + { + buf[offset] = (byte)(i >> 16); + buf[offset + 1] = (byte)(i >> 8); + buf[offset + 2] = (byte)(i); + } + + protected static void writeUint32(long i, OutputStream os) throws IOException + { + os.write((int)(i >> 24)); + os.write((int)(i >> 16)); + os.write((int)(i >> 8)); + os.write((int)(i)); + } + + protected static void writeUint32(long i, byte[] buf, int offset) + { + buf[offset] = (byte)(i >> 24); + buf[offset + 1] = (byte)(i >> 16); + buf[offset + 2] = (byte)(i >> 8); + buf[offset + 3] = (byte)(i); + } + + protected static void writeUint64(long i, OutputStream os) throws IOException + { + os.write((int)(i >> 56)); + os.write((int)(i >> 48)); + os.write((int)(i >> 40)); + os.write((int)(i >> 32)); + os.write((int)(i >> 24)); + os.write((int)(i >> 16)); + os.write((int)(i >> 8)); + os.write((int)(i)); + } + + + protected static void writeUint64(long i, byte[] buf, int offset) + { + buf[offset] = (byte)(i >> 56); + buf[offset + 1] = (byte)(i >> 48); + buf[offset + 2] = (byte)(i >> 40); + buf[offset + 3] = (byte)(i >> 32); + buf[offset + 4] = (byte)(i >> 24); + buf[offset + 5] = (byte)(i >> 16); + buf[offset + 6] = (byte)(i >> 8); + buf[offset + 7] = (byte)(i); + } + + protected static void writeOpaque8(byte[] buf, OutputStream os) throws IOException + { + writeUint8((short)buf.length, os); + os.write(buf); + } + + protected static void writeOpaque16(byte[] buf, OutputStream os) throws IOException + { + writeUint16(buf.length, os); + os.write(buf); + } + + protected static short readUint8(InputStream is) throws IOException + { + int i = is.read(); + if (i == -1) + { + throw new EOFException(); + } + return (short)i; + } + + protected static int readUint16(InputStream is) throws IOException + { + int i1 = is.read(); + int i2 = is.read(); + if ((i1 | i2) < 0) + { + throw new EOFException(); + } + return i1 << 8 | i2; + } + + protected static int readUint24(InputStream is) throws IOException + { + int i1 = is.read(); + int i2 = is.read(); + int i3 = is.read(); + if ((i1 | i2 | i3) < 0) + { + throw new EOFException(); + } + return (i1 << 16) | (i2 << 8) | i3; + } + + protected static long readUint32(InputStream is) throws IOException + { + int i1 = is.read(); + int i2 = is.read(); + int i3 = is.read(); + int i4 = is.read(); + if ((i1 | i2 | i3 | i4) < 0) + { + throw new EOFException(); + } + return (((long)i1) << 24) | (((long)i2) << 16) | (((long)i3) << 8) | ((long)i4); + } + + protected static void readFully(byte[] buf, InputStream is) throws IOException + { + int read = 0; + int i = 0; + while (read != buf.length) + { + i = is.read(buf, read, (buf.length - read)); + if (i == -1) + { + throw new EOFException(); + } + read += i; + } + } + + protected static byte[] readOpaque8(InputStream is) throws IOException + { + short length = readUint8(is); + byte[] value = new byte[length]; + readFully(value, is); + return value; + } + + protected static byte[] readOpaque16(InputStream is) throws IOException + { + int length = readUint16(is); + byte[] value = new byte[length]; + readFully(value, is); + return value; + } + + protected static void checkVersion(byte[] readVersion, TlsProtocolHandler handler) throws IOException + { + if ((readVersion[0] != 3) || (readVersion[1] != 1)) + { + handler.failWithError(TlsProtocolHandler.AL_fatal, TlsProtocolHandler.AP_protocol_version); + } + } + + protected static void checkVersion(InputStream is, TlsProtocolHandler handler) throws IOException + { + int i1 = is.read(); + int i2 = is.read(); + if ((i1 != 3) || (i2 != 1)) + { + handler.failWithError(TlsProtocolHandler.AL_fatal, TlsProtocolHandler.AP_protocol_version); + } + } + + protected static void writeVersion(OutputStream os) throws IOException + { + os.write(3); + os.write(1); + } + + private static void hmac_hash(Digest digest, byte[] secret, byte[] seed, byte[] out) + { + HMac mac = new HMac(digest); + KeyParameter param = new KeyParameter(secret); + byte[] a = seed; + int size = digest.getDigestSize(); + int iterations = (out.length + size - 1) / size; + byte[] buf = new byte[mac.getMacSize()]; + byte[] buf2 = new byte[mac.getMacSize()]; + for (int i = 0; i < iterations; i++) + { + mac.init(param); + mac.update(a, 0, a.length); + mac.doFinal(buf, 0); + a = buf; + mac.init(param); + mac.update(a, 0, a.length); + mac.update(seed, 0, seed.length); + mac.doFinal(buf2, 0); + System.arraycopy(buf2, 0, out, (size * i), Math.min(size, out.length - (size * i))); + } + } + + protected static void PRF(byte[] secret, byte[] label, byte[] seed, byte[] buf) + { + int s_half = (secret.length + 1) / 2; + byte[] s1 = new byte[s_half]; + byte[] s2 = new byte[s_half]; + System.arraycopy(secret, 0, s1, 0, s_half); + System.arraycopy(secret, secret.length - s_half, s2, 0, s_half); + + byte[] ls = new byte[label.length + seed.length]; + System.arraycopy(label, 0, ls, 0, label.length); + System.arraycopy(seed, 0, ls, label.length, seed.length); + + byte[] prf = new byte[buf.length]; + hmac_hash(new MD5Digest(), s1, ls, prf); + hmac_hash(new SHA1Digest(), s2, ls, buf); + for (int i = 0; i < buf.length; i++) + { + buf[i] ^= prf[i]; + } + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/util/Pack.java b/src/com/google/bitcoin/bouncycastle/crypto/util/Pack.java new file mode 100644 index 000000000..ad5ff91e3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/util/Pack.java @@ -0,0 +1,34 @@ +package com.google.bitcoin.bouncycastle.crypto.util; + +public abstract class Pack +{ + public static int bigEndianToInt(byte[] bs, int off) + { + int n = bs[ off] << 24; + n |= (bs[++off] & 0xff) << 16; + n |= (bs[++off] & 0xff) << 8; + n |= (bs[++off] & 0xff); + return n; + } + + public static void intToBigEndian(int n, byte[] bs, int off) + { + bs[ off] = (byte)(n >>> 24); + bs[++off] = (byte)(n >>> 16); + bs[++off] = (byte)(n >>> 8); + bs[++off] = (byte)(n ); + } + + public static long bigEndianToLong(byte[] bs, int off) + { + int hi = bigEndianToInt(bs, off); + int lo = bigEndianToInt(bs, off + 4); + return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL); + } + + public static void longToBigEndian(long n, byte[] bs, int off) + { + intToBigEndian((int)(n >>> 32), bs, off); + intToBigEndian((int)(n & 0xffffffffL), bs, off + 4); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/util/PrivateKeyFactory.java b/src/com/google/bitcoin/bouncycastle/crypto/util/PrivateKeyFactory.java new file mode 100644 index 000000000..2fae85cc5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/util/PrivateKeyFactory.java @@ -0,0 +1,190 @@ +package com.google.bitcoin.bouncycastle.crypto.util; + +import com.google.bitcoin.bouncycastle.asn1.ASN1InputStream; +import com.google.bitcoin.bouncycastle.asn1.ASN1Object; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.nist.NISTNamedCurves; +import com.google.bitcoin.bouncycastle.asn1.oiw.ElGamalParameter; +import com.google.bitcoin.bouncycastle.asn1.oiw.OIWObjectIdentifiers; +import com.google.bitcoin.bouncycastle.asn1.pkcs.DHParameter; +import com.google.bitcoin.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import com.google.bitcoin.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import com.google.bitcoin.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure; +import com.google.bitcoin.bouncycastle.asn1.sec.ECPrivateKeyStructure; +import com.google.bitcoin.bouncycastle.asn1.sec.SECNamedCurves; +import com.google.bitcoin.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; +import com.google.bitcoin.bouncycastle.asn1.x509.DSAParameter; +import com.google.bitcoin.bouncycastle.asn1.x9.X962NamedCurves; +import com.google.bitcoin.bouncycastle.asn1.x9.X962Parameters; +import com.google.bitcoin.bouncycastle.asn1.x9.X9ECParameters; +import com.google.bitcoin.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import com.google.bitcoin.bouncycastle.crypto.params.AsymmetricKeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.DHParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DSAParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DSAPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECDomainParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ElGamalParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ElGamalPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; + +/** + * Factory for creating private key objects from PKCS8 PrivateKeyInfo objects. + */ +public class PrivateKeyFactory +{ + /** + * Create a private key parameter from a PKCS8 PrivateKeyInfo encoding. + * + * @param privateKeyInfoData the PrivateKeyInfo encoding + * @return a suitable private key parameter + * @throws IOException on an error decoding the key + */ + public static AsymmetricKeyParameter createKey( + byte[] privateKeyInfoData) + throws IOException + { + return createKey( + PrivateKeyInfo.getInstance( + ASN1Object.fromByteArray(privateKeyInfoData))); + } + + /** + * Create a private key parameter from a PKCS8 PrivateKeyInfo encoding read from a stream. + * + * @param inStr the stream to read the PrivateKeyInfo encoding from + * @return a suitable private key parameter + * @throws IOException on an error decoding the key + */ + public static AsymmetricKeyParameter createKey( + InputStream inStr) + throws IOException + { + return createKey( + PrivateKeyInfo.getInstance( + new ASN1InputStream(inStr).readObject())); + } + + /** + * Create a private key parameter from the passed in PKCS8 PrivateKeyInfo object. + * + * @param keyInfo the PrivateKeyInfo object containing the key material + * @return a suitable private key parameter + * @throws IOException on an error decoding the key + */ + public static AsymmetricKeyParameter createKey( + PrivateKeyInfo keyInfo) + throws IOException + { + AlgorithmIdentifier algId = keyInfo.getAlgorithmId(); + + if (algId.getObjectId().equals(PKCSObjectIdentifiers.rsaEncryption)) + { + RSAPrivateKeyStructure keyStructure = new RSAPrivateKeyStructure((ASN1Sequence)keyInfo.getPrivateKey()); + + return new RSAPrivateCrtKeyParameters( + keyStructure.getModulus(), + keyStructure.getPublicExponent(), + keyStructure.getPrivateExponent(), + keyStructure.getPrime1(), + keyStructure.getPrime2(), + keyStructure.getExponent1(), + keyStructure.getExponent2(), + keyStructure.getCoefficient()); + } + else if (algId.getObjectId().equals(PKCSObjectIdentifiers.dhKeyAgreement)) + { + DHParameter params = new DHParameter((ASN1Sequence)keyInfo.getAlgorithmId().getParameters()); + DERInteger derX = (DERInteger)keyInfo.getPrivateKey(); + + BigInteger lVal = params.getL(); + int l = lVal == null ? 0 : lVal.intValue(); + DHParameters dhParams = new DHParameters(params.getP(), params.getG(), null, l); + + return new DHPrivateKeyParameters(derX.getValue(), dhParams); + } + else if (algId.getObjectId().equals(OIWObjectIdentifiers.elGamalAlgorithm)) + { + ElGamalParameter params = new ElGamalParameter((ASN1Sequence)keyInfo.getAlgorithmId().getParameters()); + DERInteger derX = (DERInteger)keyInfo.getPrivateKey(); + + return new ElGamalPrivateKeyParameters(derX.getValue(), new ElGamalParameters(params.getP(), params.getG())); + } + else if (algId.getObjectId().equals(X9ObjectIdentifiers.id_dsa)) + { + DERInteger derX = (DERInteger)keyInfo.getPrivateKey(); + DEREncodable de = keyInfo.getAlgorithmId().getParameters(); + + DSAParameters parameters = null; + if (de != null) + { + DSAParameter params = DSAParameter.getInstance(de.getDERObject()); + parameters = new DSAParameters(params.getP(), params.getQ(), params.getG()); + } + + return new DSAPrivateKeyParameters(derX.getValue(), parameters); + } + else if (algId.getObjectId().equals(X9ObjectIdentifiers.id_ecPublicKey)) + { + X962Parameters params = new X962Parameters((DERObject)keyInfo.getAlgorithmId().getParameters()); + ECDomainParameters dParams = null; + + if (params.isNamedCurve()) + { + DERObjectIdentifier oid = (DERObjectIdentifier)params.getParameters(); + X9ECParameters ecP = X962NamedCurves.getByOID(oid); + + if (ecP == null) + { + ecP = SECNamedCurves.getByOID(oid); + + if (ecP == null) + { + ecP = NISTNamedCurves.getByOID(oid); + + if (ecP == null) + { + ecP = TeleTrusTNamedCurves.getByOID(oid); + } + } + } + + dParams = new ECDomainParameters( + ecP.getCurve(), + ecP.getG(), + ecP.getN(), + ecP.getH(), + ecP.getSeed()); + } + else + { + X9ECParameters ecP = new X9ECParameters( + (ASN1Sequence)params.getParameters()); + dParams = new ECDomainParameters( + ecP.getCurve(), + ecP.getG(), + ecP.getN(), + ecP.getH(), + ecP.getSeed()); + } + + ECPrivateKeyStructure ec = new ECPrivateKeyStructure((ASN1Sequence)keyInfo.getPrivateKey()); + + return new ECPrivateKeyParameters(ec.getKey(), dParams); + } + else + { + throw new RuntimeException("algorithm identifier in key not recognised"); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/crypto/util/PublicKeyFactory.java b/src/com/google/bitcoin/bouncycastle/crypto/util/PublicKeyFactory.java new file mode 100644 index 000000000..c05634d88 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/crypto/util/PublicKeyFactory.java @@ -0,0 +1,194 @@ +package com.google.bitcoin.bouncycastle.crypto.util; + +import com.google.bitcoin.bouncycastle.asn1.ASN1InputStream; +import com.google.bitcoin.bouncycastle.asn1.ASN1Object; +import com.google.bitcoin.bouncycastle.asn1.ASN1OctetString; +import com.google.bitcoin.bouncycastle.asn1.ASN1Sequence; +import com.google.bitcoin.bouncycastle.asn1.DERBitString; +import com.google.bitcoin.bouncycastle.asn1.DEREncodable; +import com.google.bitcoin.bouncycastle.asn1.DERInteger; +import com.google.bitcoin.bouncycastle.asn1.DERObject; +import com.google.bitcoin.bouncycastle.asn1.DERObjectIdentifier; +import com.google.bitcoin.bouncycastle.asn1.DEROctetString; +import com.google.bitcoin.bouncycastle.asn1.nist.NISTNamedCurves; +import com.google.bitcoin.bouncycastle.asn1.oiw.ElGamalParameter; +import com.google.bitcoin.bouncycastle.asn1.oiw.OIWObjectIdentifiers; +import com.google.bitcoin.bouncycastle.asn1.pkcs.DHParameter; +import com.google.bitcoin.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import com.google.bitcoin.bouncycastle.asn1.sec.SECNamedCurves; +import com.google.bitcoin.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves; +import com.google.bitcoin.bouncycastle.asn1.x509.AlgorithmIdentifier; +import com.google.bitcoin.bouncycastle.asn1.x509.DSAParameter; +import com.google.bitcoin.bouncycastle.asn1.x509.RSAPublicKeyStructure; +import com.google.bitcoin.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import com.google.bitcoin.bouncycastle.asn1.x509.X509ObjectIdentifiers; +import com.google.bitcoin.bouncycastle.asn1.x9.X962NamedCurves; +import com.google.bitcoin.bouncycastle.asn1.x9.X962Parameters; +import com.google.bitcoin.bouncycastle.asn1.x9.X9ECParameters; +import com.google.bitcoin.bouncycastle.asn1.x9.X9ECPoint; +import com.google.bitcoin.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import com.google.bitcoin.bouncycastle.crypto.params.AsymmetricKeyParameter; +import com.google.bitcoin.bouncycastle.crypto.params.DHParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DHPublicKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DSAParameters; +import com.google.bitcoin.bouncycastle.crypto.params.DSAPublicKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECDomainParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPublicKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ElGamalParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ElGamalPublicKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.RSAKeyParameters; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; + +/** + * Factory to create asymmetric public key parameters for asymmetric ciphers + * from range of ASN.1 encoded SubjectPublicKeyInfo objects. + */ +public class PublicKeyFactory +{ + /** + * Create a public key from a SubjectPublicKeyInfo encoding + * + * @param keyInfoData the SubjectPublicKeyInfo encoding + * @return the appropriate key parameter + * @throws IOException on an error decoding the key + */ + public static AsymmetricKeyParameter createKey( + byte[] keyInfoData) + throws IOException + { + return createKey( + SubjectPublicKeyInfo.getInstance( + ASN1Object.fromByteArray(keyInfoData))); + } + + /** + * Create a public key from a SubjectPublicKeyInfo encoding read from a stream + * + * @param inStr the stream to read the SubjectPublicKeyInfo encoding from + * @return the appropriate key parameter + * @throws IOException on an error decoding the key + */ + public static AsymmetricKeyParameter createKey( + InputStream inStr) + throws IOException + { + return createKey( + SubjectPublicKeyInfo.getInstance( + new ASN1InputStream(inStr).readObject())); + } + + /** + * Create a public key from the passed in SubjectPublicKeyInfo + * + * @param keyInfo the SubjectPublicKeyInfo containing the key data + * @return the appropriate key parameter + * @throws IOException on an error decoding the key + */ + public static AsymmetricKeyParameter createKey( + SubjectPublicKeyInfo keyInfo) + throws IOException + { + AlgorithmIdentifier algId = keyInfo.getAlgorithmId(); + + if (algId.getObjectId().equals(PKCSObjectIdentifiers.rsaEncryption) + || algId.getObjectId().equals(X509ObjectIdentifiers.id_ea_rsa)) + { + RSAPublicKeyStructure pubKey = new RSAPublicKeyStructure((ASN1Sequence)keyInfo.getPublicKey()); + + return new RSAKeyParameters(false, pubKey.getModulus(), pubKey.getPublicExponent()); + } + else if (algId.getObjectId().equals(PKCSObjectIdentifiers.dhKeyAgreement) + || algId.getObjectId().equals(X9ObjectIdentifiers.dhpublicnumber)) + { + DHParameter params = new DHParameter((ASN1Sequence)keyInfo.getAlgorithmId().getParameters()); + DERInteger derY = (DERInteger)keyInfo.getPublicKey(); + + BigInteger lVal = params.getL(); + int l = lVal == null ? 0 : lVal.intValue(); + DHParameters dhParams = new DHParameters(params.getP(), params.getG(), null, l); + + return new DHPublicKeyParameters(derY.getValue(), dhParams); + } + else if (algId.getObjectId().equals(OIWObjectIdentifiers.elGamalAlgorithm)) + { + ElGamalParameter params = new ElGamalParameter((ASN1Sequence)keyInfo.getAlgorithmId().getParameters()); + DERInteger derY = (DERInteger)keyInfo.getPublicKey(); + + return new ElGamalPublicKeyParameters(derY.getValue(), new ElGamalParameters(params.getP(), params.getG())); + } + else if (algId.getObjectId().equals(X9ObjectIdentifiers.id_dsa) + || algId.getObjectId().equals(OIWObjectIdentifiers.dsaWithSHA1)) + { + DERInteger derY = (DERInteger)keyInfo.getPublicKey(); + DEREncodable de = keyInfo.getAlgorithmId().getParameters(); + + DSAParameters parameters = null; + if (de != null) + { + DSAParameter params = DSAParameter.getInstance(de.getDERObject()); + parameters = new DSAParameters(params.getP(), params.getQ(), params.getG()); + } + + return new DSAPublicKeyParameters(derY.getValue(), parameters); + } + else if (algId.getObjectId().equals(X9ObjectIdentifiers.id_ecPublicKey)) + { + X962Parameters params = new X962Parameters((DERObject)keyInfo.getAlgorithmId().getParameters()); + ECDomainParameters dParams = null; + + if (params.isNamedCurve()) + { + DERObjectIdentifier oid = (DERObjectIdentifier)params.getParameters(); + X9ECParameters ecP = X962NamedCurves.getByOID(oid); + + if (ecP == null) + { + ecP = SECNamedCurves.getByOID(oid); + + if (ecP == null) + { + ecP = NISTNamedCurves.getByOID(oid); + + if (ecP == null) + { + ecP = TeleTrusTNamedCurves.getByOID(oid); + } + } + } + + dParams = new ECDomainParameters( + ecP.getCurve(), + ecP.getG(), + ecP.getN(), + ecP.getH(), + ecP.getSeed()); + } + else + { + X9ECParameters ecP = new X9ECParameters( + (ASN1Sequence)params.getParameters()); + dParams = new ECDomainParameters( + ecP.getCurve(), + ecP.getG(), + ecP.getN(), + ecP.getH(), + ecP.getSeed()); + } + + DERBitString bits = keyInfo.getPublicKeyData(); + byte[] data = bits.getBytes(); + ASN1OctetString key = new DEROctetString(data); + + X9ECPoint derQ = new X9ECPoint(dParams.getCurve(), key); + + return new ECPublicKeyParameters(derQ.getPoint(), dParams); + } + else + { + throw new RuntimeException("algorithm identifier in key not recognised"); + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/ECAlgorithms.java b/src/com/google/bitcoin/bouncycastle/math/ec/ECAlgorithms.java new file mode 100644 index 000000000..71b7399f2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/ECAlgorithms.java @@ -0,0 +1,93 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +import java.math.BigInteger; + +public class ECAlgorithms +{ + public static ECPoint sumOfTwoMultiplies(ECPoint P, BigInteger a, + ECPoint Q, BigInteger b) + { + ECCurve c = P.getCurve(); + if (!c.equals(Q.getCurve())) + { + throw new IllegalArgumentException("P and Q must be on same curve"); + } + + // TODO Add special case back in when WTNAF is enabled +// // Point multiplication for Koblitz curves (using WTNAF) beats Shamir's trick +// if (c instanceof ECCurve.F2m) +// { +// ECCurve.F2m f2mCurve = (ECCurve.F2m) c; +// if (f2mCurve.isKoblitz()) +// { +// return P.multiply(a).add(Q.multiply(b)); +// } +// } + + return implShamirsTrick(P, a, Q, b); + } + + /* + * "Shamir's Trick", originally due to E. G. Straus + * (Addition chains of vectors. American Mathematical Monthly, + * 71(7):806-808, Aug./Sept. 1964) + *

    +     * Input: The points P, Q, scalar k = (km?, ... , k1, k0)
    +     * and scalar l = (lm?, ... , l1, l0).
    +     * Output: R = k * P + l * Q.
    +     * 1: Z <- P + Q
    +     * 2: R <- O
    +     * 3: for i from m-1 down to 0 do
    +     * 4:        R <- R + R        {point doubling}
    +     * 5:        if (ki = 1) and (li = 0) then R <- R + P end if
    +     * 6:        if (ki = 0) and (li = 1) then R <- R + Q end if
    +     * 7:        if (ki = 1) and (li = 1) then R <- R + Z end if
    +     * 8: end for
    +     * 9: return R
    +     * 
    + */ + public static ECPoint shamirsTrick(ECPoint P, BigInteger k, + ECPoint Q, BigInteger l) + { + if (!P.getCurve().equals(Q.getCurve())) + { + throw new IllegalArgumentException("P and Q must be on same curve"); + } + + return implShamirsTrick(P, k, Q, l); + } + + private static ECPoint implShamirsTrick(ECPoint P, BigInteger k, + ECPoint Q, BigInteger l) + { + int m = Math.max(k.bitLength(), l.bitLength()); + ECPoint Z = P.add(Q); + ECPoint R = P.getCurve().getInfinity(); + + for (int i = m - 1; i >= 0; --i) + { + R = R.twice(); + + if (k.testBit(i)) + { + if (l.testBit(i)) + { + R = R.add(Z); + } + else + { + R = R.add(P); + } + } + else + { + if (l.testBit(i)) + { + R = R.add(Q); + } + } + } + + return R; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/ECConstants.java b/src/com/google/bitcoin/bouncycastle/math/ec/ECConstants.java new file mode 100644 index 000000000..f5e606d2e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/ECConstants.java @@ -0,0 +1,12 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +import java.math.BigInteger; + +public interface ECConstants +{ + public static final BigInteger ZERO = BigInteger.valueOf(0); + public static final BigInteger ONE = BigInteger.valueOf(1); + public static final BigInteger TWO = BigInteger.valueOf(2); + public static final BigInteger THREE = BigInteger.valueOf(3); + public static final BigInteger FOUR = BigInteger.valueOf(4); +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/ECCurve.java b/src/com/google/bitcoin/bouncycastle/math/ec/ECCurve.java new file mode 100644 index 000000000..0c884a98e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/ECCurve.java @@ -0,0 +1,660 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +import java.math.BigInteger; +import java.util.Random; + +/** + * base class for an elliptic curve + */ +public abstract class ECCurve +{ + ECFieldElement a, b; + + public abstract int getFieldSize(); + + public abstract ECFieldElement fromBigInteger(BigInteger x); + + public abstract ECPoint createPoint(BigInteger x, BigInteger y, boolean withCompression); + + public abstract ECPoint decodePoint(byte[] encoded); + + public abstract ECPoint getInfinity(); + + public ECFieldElement getA() + { + return a; + } + + public ECFieldElement getB() + { + return b; + } + + /** + * Elliptic curve over Fp + */ + public static class Fp extends ECCurve + { + BigInteger q; + ECPoint.Fp infinity; + + public Fp(BigInteger q, BigInteger a, BigInteger b) + { + this.q = q; + this.a = fromBigInteger(a); + this.b = fromBigInteger(b); + this.infinity = new ECPoint.Fp(this, null, null); + } + + public BigInteger getQ() + { + return q; + } + + public int getFieldSize() + { + return q.bitLength(); + } + + public ECFieldElement fromBigInteger(BigInteger x) + { + return new ECFieldElement.Fp(this.q, x); + } + + public ECPoint createPoint(BigInteger x, BigInteger y, boolean withCompression) + { + return new ECPoint.Fp(this, fromBigInteger(x), fromBigInteger(y), withCompression); + } + + /** + * Decode a point on this curve from its ASN.1 encoding. The different + * encodings are taken account of, including point compression for + * Fp (X9.62 s 4.2.1 pg 17). + * @return The decoded point. + */ + public ECPoint decodePoint(byte[] encoded) + { + ECPoint p = null; + + switch (encoded[0]) + { + // infinity + case 0x00: + p = getInfinity(); + break; + // compressed + case 0x02: + case 0x03: + int ytilde = encoded[0] & 1; + byte[] i = new byte[encoded.length - 1]; + + System.arraycopy(encoded, 1, i, 0, i.length); + + ECFieldElement x = new ECFieldElement.Fp(this.q, new BigInteger(1, i)); + ECFieldElement alpha = x.multiply(x.square().add(a)).add(b); + ECFieldElement beta = alpha.sqrt(); + + // + // if we can't find a sqrt we haven't got a point on the + // curve - run! + // + if (beta == null) + { + throw new RuntimeException("Invalid point compression"); + } + + int bit0 = (beta.toBigInteger().testBit(0) ? 1 : 0); + + if (bit0 == ytilde) + { + p = new ECPoint.Fp(this, x, beta, true); + } + else + { + p = new ECPoint.Fp(this, x, + new ECFieldElement.Fp(this.q, q.subtract(beta.toBigInteger())), true); + } + break; + // uncompressed + case 0x04: + // hybrid + case 0x06: + case 0x07: + byte[] xEnc = new byte[(encoded.length - 1) / 2]; + byte[] yEnc = new byte[(encoded.length - 1) / 2]; + + System.arraycopy(encoded, 1, xEnc, 0, xEnc.length); + System.arraycopy(encoded, xEnc.length + 1, yEnc, 0, yEnc.length); + + p = new ECPoint.Fp(this, + new ECFieldElement.Fp(this.q, new BigInteger(1, xEnc)), + new ECFieldElement.Fp(this.q, new BigInteger(1, yEnc))); + break; + default: + throw new RuntimeException("Invalid point encoding 0x" + Integer.toString(encoded[0], 16)); + } + + return p; + } + + public ECPoint getInfinity() + { + return infinity; + } + + public boolean equals( + Object anObject) + { + if (anObject == this) + { + return true; + } + + if (!(anObject instanceof ECCurve.Fp)) + { + return false; + } + + ECCurve.Fp other = (ECCurve.Fp) anObject; + + return this.q.equals(other.q) + && a.equals(other.a) && b.equals(other.b); + } + + public int hashCode() + { + return a.hashCode() ^ b.hashCode() ^ q.hashCode(); + } + } + + /** + * Elliptic curves over F2m. The Weierstrass equation is given by + * y2 + xy = x3 + ax2 + b. + */ + public static class F2m extends ECCurve + { + /** + * The exponent m of F2m. + */ + private int m; // can't be final - JDK 1.1 + + /** + * TPB: The integer k where xm + + * xk + 1 represents the reduction polynomial + * f(z).
    + * PPB: The integer k1 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z).
    + */ + private int k1; // can't be final - JDK 1.1 + + /** + * TPB: Always set to 0
    + * PPB: The integer k2 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z).
    + */ + private int k2; // can't be final - JDK 1.1 + + /** + * TPB: Always set to 0
    + * PPB: The integer k3 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z).
    + */ + private int k3; // can't be final - JDK 1.1 + + /** + * The order of the base point of the curve. + */ + private BigInteger n; // can't be final - JDK 1.1 + + /** + * The cofactor of the curve. + */ + private BigInteger h; // can't be final - JDK 1.1 + + /** + * The point at infinity on this curve. + */ + private ECPoint.F2m infinity; // can't be final - JDK 1.1 + + /** + * The parameter μ of the elliptic curve if this is + * a Koblitz curve. + */ + private byte mu = 0; + + /** + * The auxiliary values s0 and + * s1 used for partial modular reduction for + * Koblitz curves. + */ + private BigInteger[] si = null; + + /** + * Constructor for Trinomial Polynomial Basis (TPB). + * @param m The exponent m of + * F2m. + * @param k The integer k where xm + + * xk + 1 represents the reduction + * polynomial f(z). + * @param a The coefficient a in the Weierstrass equation + * for non-supersingular elliptic curves over + * F2m. + * @param b The coefficient b in the Weierstrass equation + * for non-supersingular elliptic curves over + * F2m. + */ + public F2m( + int m, + int k, + BigInteger a, + BigInteger b) + { + this(m, k, 0, 0, a, b, null, null); + } + + /** + * Constructor for Trinomial Polynomial Basis (TPB). + * @param m The exponent m of + * F2m. + * @param k The integer k where xm + + * xk + 1 represents the reduction + * polynomial f(z). + * @param a The coefficient a in the Weierstrass equation + * for non-supersingular elliptic curves over + * F2m. + * @param b The coefficient b in the Weierstrass equation + * for non-supersingular elliptic curves over + * F2m. + * @param n The order of the main subgroup of the elliptic curve. + * @param h The cofactor of the elliptic curve, i.e. + * #Ea(F2m) = h * n. + */ + public F2m( + int m, + int k, + BigInteger a, + BigInteger b, + BigInteger n, + BigInteger h) + { + this(m, k, 0, 0, a, b, n, h); + } + + /** + * Constructor for Pentanomial Polynomial Basis (PPB). + * @param m The exponent m of + * F2m. + * @param k1 The integer k1 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z). + * @param k2 The integer k2 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z). + * @param k3 The integer k3 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z). + * @param a The coefficient a in the Weierstrass equation + * for non-supersingular elliptic curves over + * F2m. + * @param b The coefficient b in the Weierstrass equation + * for non-supersingular elliptic curves over + * F2m. + */ + public F2m( + int m, + int k1, + int k2, + int k3, + BigInteger a, + BigInteger b) + { + this(m, k1, k2, k3, a, b, null, null); + } + + /** + * Constructor for Pentanomial Polynomial Basis (PPB). + * @param m The exponent m of + * F2m. + * @param k1 The integer k1 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z). + * @param k2 The integer k2 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z). + * @param k3 The integer k3 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z). + * @param a The coefficient a in the Weierstrass equation + * for non-supersingular elliptic curves over + * F2m. + * @param b The coefficient b in the Weierstrass equation + * for non-supersingular elliptic curves over + * F2m. + * @param n The order of the main subgroup of the elliptic curve. + * @param h The cofactor of the elliptic curve, i.e. + * #Ea(F2m) = h * n. + */ + public F2m( + int m, + int k1, + int k2, + int k3, + BigInteger a, + BigInteger b, + BigInteger n, + BigInteger h) + { + this.m = m; + this.k1 = k1; + this.k2 = k2; + this.k3 = k3; + this.n = n; + this.h = h; + + if (k1 == 0) + { + throw new IllegalArgumentException("k1 must be > 0"); + } + + if (k2 == 0) + { + if (k3 != 0) + { + throw new IllegalArgumentException("k3 must be 0 if k2 == 0"); + } + } + else + { + if (k2 <= k1) + { + throw new IllegalArgumentException("k2 must be > k1"); + } + + if (k3 <= k2) + { + throw new IllegalArgumentException("k3 must be > k2"); + } + } + + this.a = fromBigInteger(a); + this.b = fromBigInteger(b); + this.infinity = new ECPoint.F2m(this, null, null); + } + + public int getFieldSize() + { + return m; + } + + public ECFieldElement fromBigInteger(BigInteger x) + { + return new ECFieldElement.F2m(this.m, this.k1, this.k2, this.k3, x); + } + + public ECPoint createPoint(BigInteger x, BigInteger y, boolean withCompression) + { + return new ECPoint.F2m(this, fromBigInteger(x), fromBigInteger(y), withCompression); + } + + /* (non-Javadoc) + * @see org.bouncycastle.math.ec.ECCurve#decodePoint(byte[]) + */ + public ECPoint decodePoint(byte[] encoded) + { + ECPoint p = null; + + switch (encoded[0]) + { + // infinity + case 0x00: + p = getInfinity(); + break; + // compressed + case 0x02: + case 0x03: + byte[] enc = new byte[encoded.length - 1]; + System.arraycopy(encoded, 1, enc, 0, enc.length); + if (encoded[0] == 0x02) + { + p = decompressPoint(enc, 0); + } + else + { + p = decompressPoint(enc, 1); + } + break; + // uncompressed + case 0x04: + // hybrid + case 0x06: + case 0x07: + byte[] xEnc = new byte[(encoded.length - 1) / 2]; + byte[] yEnc = new byte[(encoded.length - 1) / 2]; + + System.arraycopy(encoded, 1, xEnc, 0, xEnc.length); + System.arraycopy(encoded, xEnc.length + 1, yEnc, 0, yEnc.length); + + p = new ECPoint.F2m(this, + new ECFieldElement.F2m(this.m, this.k1, this.k2, this.k3, + new BigInteger(1, xEnc)), + new ECFieldElement.F2m(this.m, this.k1, this.k2, this.k3, + new BigInteger(1, yEnc)), false); + break; + + default: + throw new RuntimeException("Invalid point encoding 0x" + Integer.toString(encoded[0], 16)); + } + + return p; + } + + public ECPoint getInfinity() + { + return infinity; + } + + /** + * Returns true if this is a Koblitz curve (ABC curve). + * @return true if this is a Koblitz curve (ABC curve), false otherwise + */ + public boolean isKoblitz() + { + return ((n != null) && (h != null) && + ((a.toBigInteger().equals(ECConstants.ZERO)) || + (a.toBigInteger().equals(ECConstants.ONE))) && + (b.toBigInteger().equals(ECConstants.ONE))); + } + + /** + * Returns the parameter μ of the elliptic curve. + * @return μ of the elliptic curve. + * @throws IllegalArgumentException if the given ECCurve is not a + * Koblitz curve. + */ + synchronized byte getMu() + { + if (mu == 0) + { + mu = Tnaf.getMu(this); + } + return mu; + } + + /** + * @return the auxiliary values s0 and + * s1 used for partial modular reduction for + * Koblitz curves. + */ + synchronized BigInteger[] getSi() + { + if (si == null) + { + si = Tnaf.getSi(this); + } + return si; + } + + /** + * Decompresses a compressed point P = (xp, yp) (X9.62 s 4.2.2). + * + * @param xEnc + * The encoding of field element xp. + * @param ypBit + * ~yp, an indication bit for the decompression of yp. + * @return the decompressed point. + */ + private ECPoint decompressPoint( + byte[] xEnc, + int ypBit) + { + ECFieldElement xp = new ECFieldElement.F2m( + this.m, this.k1, this.k2, this.k3, new BigInteger(1, xEnc)); + ECFieldElement yp = null; + if (xp.toBigInteger().equals(ECConstants.ZERO)) + { + yp = (ECFieldElement.F2m)b; + for (int i = 0; i < m - 1; i++) + { + yp = yp.square(); + } + } + else + { + ECFieldElement beta = xp.add(a).add( + b.multiply(xp.square().invert())); + ECFieldElement z = solveQuadradicEquation(beta); + if (z == null) + { + throw new RuntimeException("Invalid point compression"); + } + int zBit = 0; + if (z.toBigInteger().testBit(0)) + { + zBit = 1; + } + if (zBit != ypBit) + { + z = z.add(new ECFieldElement.F2m(this.m, this.k1, this.k2, + this.k3, ECConstants.ONE)); + } + yp = xp.multiply(z); + } + + return new ECPoint.F2m(this, xp, yp); + } + + /** + * Solves a quadratic equation z2 + z = beta(X9.62 + * D.1.6) The other solution is z + 1. + * + * @param beta + * The value to solve the qradratic equation for. + * @return the solution for z2 + z = beta or + * null if no solution exists. + */ + private ECFieldElement solveQuadradicEquation(ECFieldElement beta) + { + ECFieldElement zeroElement = new ECFieldElement.F2m( + this.m, this.k1, this.k2, this.k3, ECConstants.ZERO); + + if (beta.toBigInteger().equals(ECConstants.ZERO)) + { + return zeroElement; + } + + ECFieldElement z = null; + ECFieldElement gamma = zeroElement; + + Random rand = new Random(); + do + { + ECFieldElement t = new ECFieldElement.F2m(this.m, this.k1, + this.k2, this.k3, new BigInteger(m, rand)); + z = zeroElement; + ECFieldElement w = beta; + for (int i = 1; i <= m - 1; i++) + { + ECFieldElement w2 = w.square(); + z = z.square().add(w2.multiply(t)); + w = w2.add(beta); + } + if (!w.toBigInteger().equals(ECConstants.ZERO)) + { + return null; + } + gamma = z.square().add(z); + } + while (gamma.toBigInteger().equals(ECConstants.ZERO)); + + return z; + } + + public boolean equals( + Object anObject) + { + if (anObject == this) + { + return true; + } + + if (!(anObject instanceof ECCurve.F2m)) + { + return false; + } + + ECCurve.F2m other = (ECCurve.F2m)anObject; + + return (this.m == other.m) && (this.k1 == other.k1) + && (this.k2 == other.k2) && (this.k3 == other.k3) + && a.equals(other.a) && b.equals(other.b); + } + + public int hashCode() + { + return this.a.hashCode() ^ this.b.hashCode() ^ m ^ k1 ^ k2 ^ k3; + } + + public int getM() + { + return m; + } + + /** + * Return true if curve uses a Trinomial basis. + * + * @return true if curve Trinomial, false otherwise. + */ + public boolean isTrinomial() + { + return k2 == 0 && k3 == 0; + } + + public int getK1() + { + return k1; + } + + public int getK2() + { + return k2; + } + + public int getK3() + { + return k3; + } + + public BigInteger getN() + { + return n; + } + + public BigInteger getH() + { + return h; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/ECFieldElement.java b/src/com/google/bitcoin/bouncycastle/math/ec/ECFieldElement.java new file mode 100644 index 000000000..7858aa3ac --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/ECFieldElement.java @@ -0,0 +1,1196 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +import java.math.BigInteger; +import java.util.Random; + +public abstract class ECFieldElement + implements ECConstants +{ + + public abstract BigInteger toBigInteger(); + public abstract String getFieldName(); + public abstract int getFieldSize(); + public abstract ECFieldElement add(ECFieldElement b); + public abstract ECFieldElement subtract(ECFieldElement b); + public abstract ECFieldElement multiply(ECFieldElement b); + public abstract ECFieldElement divide(ECFieldElement b); + public abstract ECFieldElement negate(); + public abstract ECFieldElement square(); + public abstract ECFieldElement invert(); + public abstract ECFieldElement sqrt(); + + public String toString() + { + return this.toBigInteger().toString(2); + } + + public static class Fp extends ECFieldElement + { + BigInteger x; + + BigInteger q; + + public Fp(BigInteger q, BigInteger x) + { + this.x = x; + + if (x.compareTo(q) >= 0) + { + throw new IllegalArgumentException("x value too large in field element"); + } + + this.q = q; + } + + public BigInteger toBigInteger() + { + return x; + } + + /** + * return the field name for this field. + * + * @return the string "Fp". + */ + public String getFieldName() + { + return "Fp"; + } + + public int getFieldSize() + { + return q.bitLength(); + } + + public BigInteger getQ() + { + return q; + } + + public ECFieldElement add(ECFieldElement b) + { + return new Fp(q, x.add(b.toBigInteger()).mod(q)); + } + + public ECFieldElement subtract(ECFieldElement b) + { + return new Fp(q, x.subtract(b.toBigInteger()).mod(q)); + } + + public ECFieldElement multiply(ECFieldElement b) + { + return new Fp(q, x.multiply(b.toBigInteger()).mod(q)); + } + + public ECFieldElement divide(ECFieldElement b) + { + return new Fp(q, x.multiply(b.toBigInteger().modInverse(q)).mod(q)); + } + + public ECFieldElement negate() + { + return new Fp(q, x.negate().mod(q)); + } + + public ECFieldElement square() + { + return new Fp(q, x.multiply(x).mod(q)); + } + + public ECFieldElement invert() + { + return new Fp(q, x.modInverse(q)); + } + + // D.1.4 91 + /** + * return a sqrt root - the routine verifies that the calculation + * returns the right value - if none exists it returns null. + */ + public ECFieldElement sqrt() + { + if (!q.testBit(0)) + { + throw new RuntimeException("not done yet"); + } + + // note: even though this class implements ECConstants don't be tempted to + // remove the explicit declaration, some J2ME environments don't cope. + // p mod 4 == 3 + if (q.testBit(1)) + { + // z = g^(u+1) + p, p = 4u + 3 + ECFieldElement z = new Fp(q, x.modPow(q.shiftRight(2).add(ECConstants.ONE), q)); + + return z.square().equals(this) ? z : null; + } + + // p mod 4 == 1 + BigInteger qMinusOne = q.subtract(ECConstants.ONE); + + BigInteger legendreExponent = qMinusOne.shiftRight(1); + if (!(x.modPow(legendreExponent, q).equals(ECConstants.ONE))) + { + return null; + } + + BigInteger u = qMinusOne.shiftRight(2); + BigInteger k = u.shiftLeft(1).add(ECConstants.ONE); + + BigInteger Q = this.x; + BigInteger fourQ = Q.shiftLeft(2).mod(q); + + BigInteger U, V; + Random rand = new Random(); + do + { + BigInteger P; + do + { + P = new BigInteger(q.bitLength(), rand); + } + while (P.compareTo(q) >= 0 + || !(P.multiply(P).subtract(fourQ).modPow(legendreExponent, q).equals(qMinusOne))); + + BigInteger[] result = lucasSequence(q, P, Q, k); + U = result[0]; + V = result[1]; + + if (V.multiply(V).mod(q).equals(fourQ)) + { + // Integer division by 2, mod q + if (V.testBit(0)) + { + V = V.add(q); + } + + V = V.shiftRight(1); + + //assert V.multiply(V).mod(q).equals(x); + + return new ECFieldElement.Fp(q, V); + } + } + while (U.equals(ECConstants.ONE) || U.equals(qMinusOne)); + + return null; + +// BigInteger qMinusOne = q.subtract(ECConstants.ONE); +// BigInteger legendreExponent = qMinusOne.shiftRight(1); //divide(ECConstants.TWO); +// if (!(x.modPow(legendreExponent, q).equals(ECConstants.ONE))) +// { +// return null; +// } +// +// Random rand = new Random(); +// BigInteger fourX = x.shiftLeft(2); +// +// BigInteger r; +// do +// { +// r = new BigInteger(q.bitLength(), rand); +// } +// while (r.compareTo(q) >= 0 +// || !(r.multiply(r).subtract(fourX).modPow(legendreExponent, q).equals(qMinusOne))); +// +// BigInteger n1 = qMinusOne.shiftRight(2); //.divide(ECConstants.FOUR); +// BigInteger n2 = n1.add(ECConstants.ONE); //q.add(ECConstants.THREE).divide(ECConstants.FOUR); +// +// BigInteger wOne = WOne(r, x, q); +// BigInteger wSum = W(n1, wOne, q).add(W(n2, wOne, q)).mod(q); +// BigInteger twoR = r.shiftLeft(1); //ECConstants.TWO.multiply(r); +// +// BigInteger root = twoR.modPow(q.subtract(ECConstants.TWO), q) +// .multiply(x).mod(q) +// .multiply(wSum).mod(q); +// +// return new Fp(q, root); + } + +// private static BigInteger W(BigInteger n, BigInteger wOne, BigInteger p) +// { +// if (n.equals(ECConstants.ONE)) +// { +// return wOne; +// } +// boolean isEven = !n.testBit(0); +// n = n.shiftRight(1);//divide(ECConstants.TWO); +// if (isEven) +// { +// BigInteger w = W(n, wOne, p); +// return w.multiply(w).subtract(ECConstants.TWO).mod(p); +// } +// BigInteger w1 = W(n.add(ECConstants.ONE), wOne, p); +// BigInteger w2 = W(n, wOne, p); +// return w1.multiply(w2).subtract(wOne).mod(p); +// } +// +// private BigInteger WOne(BigInteger r, BigInteger x, BigInteger p) +// { +// return r.multiply(r).multiply(x.modPow(q.subtract(ECConstants.TWO), q)).subtract(ECConstants.TWO).mod(p); +// } + + private static BigInteger[] lucasSequence( + BigInteger p, + BigInteger P, + BigInteger Q, + BigInteger k) + { + int n = k.bitLength(); + int s = k.getLowestSetBit(); + + BigInteger Uh = ECConstants.ONE; + BigInteger Vl = ECConstants.TWO; + BigInteger Vh = P; + BigInteger Ql = ECConstants.ONE; + BigInteger Qh = ECConstants.ONE; + + for (int j = n - 1; j >= s + 1; --j) + { + Ql = Ql.multiply(Qh).mod(p); + + if (k.testBit(j)) + { + Qh = Ql.multiply(Q).mod(p); + Uh = Uh.multiply(Vh).mod(p); + Vl = Vh.multiply(Vl).subtract(P.multiply(Ql)).mod(p); + Vh = Vh.multiply(Vh).subtract(Qh.shiftLeft(1)).mod(p); + } + else + { + Qh = Ql; + Uh = Uh.multiply(Vl).subtract(Ql).mod(p); + Vh = Vh.multiply(Vl).subtract(P.multiply(Ql)).mod(p); + Vl = Vl.multiply(Vl).subtract(Ql.shiftLeft(1)).mod(p); + } + } + + Ql = Ql.multiply(Qh).mod(p); + Qh = Ql.multiply(Q).mod(p); + Uh = Uh.multiply(Vl).subtract(Ql).mod(p); + Vl = Vh.multiply(Vl).subtract(P.multiply(Ql)).mod(p); + Ql = Ql.multiply(Qh).mod(p); + + for (int j = 1; j <= s; ++j) + { + Uh = Uh.multiply(Vl).mod(p); + Vl = Vl.multiply(Vl).subtract(Ql.shiftLeft(1)).mod(p); + Ql = Ql.multiply(Ql).mod(p); + } + + return new BigInteger[]{ Uh, Vl }; + } + + public boolean equals(Object other) + { + if (other == this) + { + return true; + } + + if (!(other instanceof ECFieldElement.Fp)) + { + return false; + } + + ECFieldElement.Fp o = (ECFieldElement.Fp)other; + return q.equals(o.q) && x.equals(o.x); + } + + public int hashCode() + { + return q.hashCode() ^ x.hashCode(); + } + } + +// /** +// * Class representing the Elements of the finite field +// * F2m in polynomial basis (PB) +// * representation. Both trinomial (TPB) and pentanomial (PPB) polynomial +// * basis representations are supported. Gaussian normal basis (GNB) +// * representation is not supported. +// */ +// public static class F2m extends ECFieldElement +// { +// BigInteger x; +// +// /** +// * Indicates gaussian normal basis representation (GNB). Number chosen +// * according to X9.62. GNB is not implemented at present. +// */ +// public static final int GNB = 1; +// +// /** +// * Indicates trinomial basis representation (TPB). Number chosen +// * according to X9.62. +// */ +// public static final int TPB = 2; +// +// /** +// * Indicates pentanomial basis representation (PPB). Number chosen +// * according to X9.62. +// */ +// public static final int PPB = 3; +// +// /** +// * TPB or PPB. +// */ +// private int representation; +// +// /** +// * The exponent m of F2m. +// */ +// private int m; +// +// /** +// * TPB: The integer k where xm + +// * xk + 1 represents the reduction polynomial +// * f(z).
    +// * PPB: The integer k1 where xm + +// * xk3 + xk2 + xk1 + 1 +// * represents the reduction polynomial f(z).
    +// */ +// private int k1; +// +// /** +// * TPB: Always set to 0
    +// * PPB: The integer k2 where xm + +// * xk3 + xk2 + xk1 + 1 +// * represents the reduction polynomial f(z).
    +// */ +// private int k2; +// +// /** +// * TPB: Always set to 0
    +// * PPB: The integer k3 where xm + +// * xk3 + xk2 + xk1 + 1 +// * represents the reduction polynomial f(z).
    +// */ +// private int k3; +// +// /** +// * Constructor for PPB. +// * @param m The exponent m of +// * F2m. +// * @param k1 The integer k1 where xm + +// * xk3 + xk2 + xk1 + 1 +// * represents the reduction polynomial f(z). +// * @param k2 The integer k2 where xm + +// * xk3 + xk2 + xk1 + 1 +// * represents the reduction polynomial f(z). +// * @param k3 The integer k3 where xm + +// * xk3 + xk2 + xk1 + 1 +// * represents the reduction polynomial f(z). +// * @param x The BigInteger representing the value of the field element. +// */ +// public F2m( +// int m, +// int k1, +// int k2, +// int k3, +// BigInteger x) +// { +//// super(x); +// this.x = x; +// +// if ((k2 == 0) && (k3 == 0)) +// { +// this.representation = TPB; +// } +// else +// { +// if (k2 >= k3) +// { +// throw new IllegalArgumentException( +// "k2 must be smaller than k3"); +// } +// if (k2 <= 0) +// { +// throw new IllegalArgumentException( +// "k2 must be larger than 0"); +// } +// this.representation = PPB; +// } +// +// if (x.signum() < 0) +// { +// throw new IllegalArgumentException("x value cannot be negative"); +// } +// +// this.m = m; +// this.k1 = k1; +// this.k2 = k2; +// this.k3 = k3; +// } +// +// /** +// * Constructor for TPB. +// * @param m The exponent m of +// * F2m. +// * @param k The integer k where xm + +// * xk + 1 represents the reduction +// * polynomial f(z). +// * @param x The BigInteger representing the value of the field element. +// */ +// public F2m(int m, int k, BigInteger x) +// { +// // Set k1 to k, and set k2 and k3 to 0 +// this(m, k, 0, 0, x); +// } +// +// public BigInteger toBigInteger() +// { +// return x; +// } +// +// public String getFieldName() +// { +// return "F2m"; +// } +// +// public int getFieldSize() +// { +// return m; +// } +// +// /** +// * Checks, if the ECFieldElements a and b +// * are elements of the same field F2m +// * (having the same representation). +// * @param a field element. +// * @param b field element to be compared. +// * @throws IllegalArgumentException if a and b +// * are not elements of the same field +// * F2m (having the same +// * representation). +// */ +// public static void checkFieldElements( +// ECFieldElement a, +// ECFieldElement b) +// { +// if ((!(a instanceof F2m)) || (!(b instanceof F2m))) +// { +// throw new IllegalArgumentException("Field elements are not " +// + "both instances of ECFieldElement.F2m"); +// } +// +// if ((a.toBigInteger().signum() < 0) || (b.toBigInteger().signum() < 0)) +// { +// throw new IllegalArgumentException( +// "x value may not be negative"); +// } +// +// ECFieldElement.F2m aF2m = (ECFieldElement.F2m)a; +// ECFieldElement.F2m bF2m = (ECFieldElement.F2m)b; +// +// if ((aF2m.m != bF2m.m) || (aF2m.k1 != bF2m.k1) +// || (aF2m.k2 != bF2m.k2) || (aF2m.k3 != bF2m.k3)) +// { +// throw new IllegalArgumentException("Field elements are not " +// + "elements of the same field F2m"); +// } +// +// if (aF2m.representation != bF2m.representation) +// { +// // Should never occur +// throw new IllegalArgumentException( +// "One of the field " +// + "elements are not elements has incorrect representation"); +// } +// } +// +// /** +// * Computes z * a(z) mod f(z), where f(z) is +// * the reduction polynomial of this. +// * @param a The polynomial a(z) to be multiplied by +// * z mod f(z). +// * @return z * a(z) mod f(z) +// */ +// private BigInteger multZModF(final BigInteger a) +// { +// // Left-shift of a(z) +// BigInteger az = a.shiftLeft(1); +// if (az.testBit(this.m)) +// { +// // If the coefficient of z^m in a(z) equals 1, reduction +// // modulo f(z) is performed: Add f(z) to to a(z): +// // Step 1: Unset mth coeffient of a(z) +// az = az.clearBit(this.m); +// +// // Step 2: Add r(z) to a(z), where r(z) is defined as +// // f(z) = z^m + r(z), and k1, k2, k3 are the positions of +// // the non-zero coefficients in r(z) +// az = az.flipBit(0); +// az = az.flipBit(this.k1); +// if (this.representation == PPB) +// { +// az = az.flipBit(this.k2); +// az = az.flipBit(this.k3); +// } +// } +// return az; +// } +// +// public ECFieldElement add(final ECFieldElement b) +// { +// // No check performed here for performance reasons. Instead the +// // elements involved are checked in ECPoint.F2m +// // checkFieldElements(this, b); +// if (b.toBigInteger().signum() == 0) +// { +// return this; +// } +// +// return new F2m(this.m, this.k1, this.k2, this.k3, this.x.xor(b.toBigInteger())); +// } +// +// public ECFieldElement subtract(final ECFieldElement b) +// { +// // Addition and subtraction are the same in F2m +// return add(b); +// } +// +// +// public ECFieldElement multiply(final ECFieldElement b) +// { +// // Left-to-right shift-and-add field multiplication in F2m +// // Input: Binary polynomials a(z) and b(z) of degree at most m-1 +// // Output: c(z) = a(z) * b(z) mod f(z) +// +// // No check performed here for performance reasons. Instead the +// // elements involved are checked in ECPoint.F2m +// // checkFieldElements(this, b); +// final BigInteger az = this.x; +// BigInteger bz = b.toBigInteger(); +// BigInteger cz; +// +// // Compute c(z) = a(z) * b(z) mod f(z) +// if (az.testBit(0)) +// { +// cz = bz; +// } +// else +// { +// cz = ECConstants.ZERO; +// } +// +// for (int i = 1; i < this.m; i++) +// { +// // b(z) := z * b(z) mod f(z) +// bz = multZModF(bz); +// +// if (az.testBit(i)) +// { +// // If the coefficient of x^i in a(z) equals 1, b(z) is added +// // to c(z) +// cz = cz.xor(bz); +// } +// } +// return new ECFieldElement.F2m(m, this.k1, this.k2, this.k3, cz); +// } +// +// +// public ECFieldElement divide(final ECFieldElement b) +// { +// // There may be more efficient implementations +// ECFieldElement bInv = b.invert(); +// return multiply(bInv); +// } +// +// public ECFieldElement negate() +// { +// // -x == x holds for all x in F2m +// return this; +// } +// +// public ECFieldElement square() +// { +// // Naive implementation, can probably be speeded up using modular +// // reduction +// return multiply(this); +// } +// +// public ECFieldElement invert() +// { +// // Inversion in F2m using the extended Euclidean algorithm +// // Input: A nonzero polynomial a(z) of degree at most m-1 +// // Output: a(z)^(-1) mod f(z) +// +// // u(z) := a(z) +// BigInteger uz = this.x; +// if (uz.signum() <= 0) +// { +// throw new ArithmeticException("x is zero or negative, " + +// "inversion is impossible"); +// } +// +// // v(z) := f(z) +// BigInteger vz = ECConstants.ZERO.setBit(m); +// vz = vz.setBit(0); +// vz = vz.setBit(this.k1); +// if (this.representation == PPB) +// { +// vz = vz.setBit(this.k2); +// vz = vz.setBit(this.k3); +// } +// +// // g1(z) := 1, g2(z) := 0 +// BigInteger g1z = ECConstants.ONE; +// BigInteger g2z = ECConstants.ZERO; +// +// // while u != 1 +// while (!(uz.equals(ECConstants.ZERO))) +// { +// // j := deg(u(z)) - deg(v(z)) +// int j = uz.bitLength() - vz.bitLength(); +// +// // If j < 0 then: u(z) <-> v(z), g1(z) <-> g2(z), j := -j +// if (j < 0) +// { +// final BigInteger uzCopy = uz; +// uz = vz; +// vz = uzCopy; +// +// final BigInteger g1zCopy = g1z; +// g1z = g2z; +// g2z = g1zCopy; +// +// j = -j; +// } +// +// // u(z) := u(z) + z^j * v(z) +// // Note, that no reduction modulo f(z) is required, because +// // deg(u(z) + z^j * v(z)) <= max(deg(u(z)), j + deg(v(z))) +// // = max(deg(u(z)), deg(u(z)) - deg(v(z)) + deg(v(z)) +// // = deg(u(z)) +// uz = uz.xor(vz.shiftLeft(j)); +// +// // g1(z) := g1(z) + z^j * g2(z) +// g1z = g1z.xor(g2z.shiftLeft(j)); +//// if (g1z.bitLength() > this.m) { +//// throw new ArithmeticException( +//// "deg(g1z) >= m, g1z = " + g1z.toString(2)); +//// } +// } +// return new ECFieldElement.F2m( +// this.m, this.k1, this.k2, this.k3, g2z); +// } +// +// public ECFieldElement sqrt() +// { +// throw new RuntimeException("Not implemented"); +// } +// +// /** +// * @return the representation of the field +// * F2m, either of +// * TPB (trinomial +// * basis representation) or +// * PPB (pentanomial +// * basis representation). +// */ +// public int getRepresentation() +// { +// return this.representation; +// } +// +// /** +// * @return the degree m of the reduction polynomial +// * f(z). +// */ +// public int getM() +// { +// return this.m; +// } +// +// /** +// * @return TPB: The integer k where xm + +// * xk + 1 represents the reduction polynomial +// * f(z).
    +// * PPB: The integer k1 where xm + +// * xk3 + xk2 + xk1 + 1 +// * represents the reduction polynomial f(z).
    +// */ +// public int getK1() +// { +// return this.k1; +// } +// +// /** +// * @return TPB: Always returns 0
    +// * PPB: The integer k2 where xm + +// * xk3 + xk2 + xk1 + 1 +// * represents the reduction polynomial f(z).
    +// */ +// public int getK2() +// { +// return this.k2; +// } +// +// /** +// * @return TPB: Always set to 0
    +// * PPB: The integer k3 where xm + +// * xk3 + xk2 + xk1 + 1 +// * represents the reduction polynomial f(z).
    +// */ +// public int getK3() +// { +// return this.k3; +// } +// +// public boolean equals(Object anObject) +// { +// if (anObject == this) +// { +// return true; +// } +// +// if (!(anObject instanceof ECFieldElement.F2m)) +// { +// return false; +// } +// +// ECFieldElement.F2m b = (ECFieldElement.F2m)anObject; +// +// return ((this.m == b.m) && (this.k1 == b.k1) && (this.k2 == b.k2) +// && (this.k3 == b.k3) +// && (this.representation == b.representation) +// && (this.x.equals(b.x))); +// } +// +// public int hashCode() +// { +// return x.hashCode() ^ m ^ k1 ^ k2 ^ k3; +// } +// } + + /** + * Class representing the Elements of the finite field + * F2m in polynomial basis (PB) + * representation. Both trinomial (TPB) and pentanomial (PPB) polynomial + * basis representations are supported. Gaussian normal basis (GNB) + * representation is not supported. + */ + public static class F2m extends ECFieldElement + { + /** + * Indicates gaussian normal basis representation (GNB). Number chosen + * according to X9.62. GNB is not implemented at present. + */ + public static final int GNB = 1; + + /** + * Indicates trinomial basis representation (TPB). Number chosen + * according to X9.62. + */ + public static final int TPB = 2; + + /** + * Indicates pentanomial basis representation (PPB). Number chosen + * according to X9.62. + */ + public static final int PPB = 3; + + /** + * TPB or PPB. + */ + private int representation; + + /** + * The exponent m of F2m. + */ + private int m; + + /** + * TPB: The integer k where xm + + * xk + 1 represents the reduction polynomial + * f(z).
    + * PPB: The integer k1 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z).
    + */ + private int k1; + + /** + * TPB: Always set to 0
    + * PPB: The integer k2 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z).
    + */ + private int k2; + + /** + * TPB: Always set to 0
    + * PPB: The integer k3 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z).
    + */ + private int k3; + + /** + * The IntArray holding the bits. + */ + private IntArray x; + + /** + * The number of ints required to hold m bits. + */ + private int t; + + /** + * Constructor for PPB. + * @param m The exponent m of + * F2m. + * @param k1 The integer k1 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z). + * @param k2 The integer k2 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z). + * @param k3 The integer k3 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z). + * @param x The BigInteger representing the value of the field element. + */ + public F2m( + int m, + int k1, + int k2, + int k3, + BigInteger x) + { + // t = m / 32 rounded up to the next integer + t = (m + 31) >> 5; + this.x = new IntArray(x, t); + + if ((k2 == 0) && (k3 == 0)) + { + this.representation = TPB; + } + else + { + if (k2 >= k3) + { + throw new IllegalArgumentException( + "k2 must be smaller than k3"); + } + if (k2 <= 0) + { + throw new IllegalArgumentException( + "k2 must be larger than 0"); + } + this.representation = PPB; + } + + if (x.signum() < 0) + { + throw new IllegalArgumentException("x value cannot be negative"); + } + + this.m = m; + this.k1 = k1; + this.k2 = k2; + this.k3 = k3; + } + + /** + * Constructor for TPB. + * @param m The exponent m of + * F2m. + * @param k The integer k where xm + + * xk + 1 represents the reduction + * polynomial f(z). + * @param x The BigInteger representing the value of the field element. + */ + public F2m(int m, int k, BigInteger x) + { + // Set k1 to k, and set k2 and k3 to 0 + this(m, k, 0, 0, x); + } + + private F2m(int m, int k1, int k2, int k3, IntArray x) + { + t = (m + 31) >> 5; + this.x = x; + this.m = m; + this.k1 = k1; + this.k2 = k2; + this.k3 = k3; + + if ((k2 == 0) && (k3 == 0)) + { + this.representation = TPB; + } + else + { + this.representation = PPB; + } + + } + + public BigInteger toBigInteger() + { + return x.toBigInteger(); + } + + public String getFieldName() + { + return "F2m"; + } + + public int getFieldSize() + { + return m; + } + + /** + * Checks, if the ECFieldElements a and b + * are elements of the same field F2m + * (having the same representation). + * @param a field element. + * @param b field element to be compared. + * @throws IllegalArgumentException if a and b + * are not elements of the same field + * F2m (having the same + * representation). + */ + public static void checkFieldElements( + ECFieldElement a, + ECFieldElement b) + { + if ((!(a instanceof F2m)) || (!(b instanceof F2m))) + { + throw new IllegalArgumentException("Field elements are not " + + "both instances of ECFieldElement.F2m"); + } + + ECFieldElement.F2m aF2m = (ECFieldElement.F2m)a; + ECFieldElement.F2m bF2m = (ECFieldElement.F2m)b; + + if ((aF2m.m != bF2m.m) || (aF2m.k1 != bF2m.k1) + || (aF2m.k2 != bF2m.k2) || (aF2m.k3 != bF2m.k3)) + { + throw new IllegalArgumentException("Field elements are not " + + "elements of the same field F2m"); + } + + if (aF2m.representation != bF2m.representation) + { + // Should never occur + throw new IllegalArgumentException( + "One of the field " + + "elements are not elements has incorrect representation"); + } + } + + public ECFieldElement add(final ECFieldElement b) + { + // No check performed here for performance reasons. Instead the + // elements involved are checked in ECPoint.F2m + // checkFieldElements(this, b); + IntArray iarrClone = (IntArray)this.x.clone(); + F2m bF2m = (F2m)b; + iarrClone.addShifted(bF2m.x, 0); + return new F2m(m, k1, k2, k3, iarrClone); + } + + public ECFieldElement subtract(final ECFieldElement b) + { + // Addition and subtraction are the same in F2m + return add(b); + } + + public ECFieldElement multiply(final ECFieldElement b) + { + // Right-to-left comb multiplication in the IntArray + // Input: Binary polynomials a(z) and b(z) of degree at most m-1 + // Output: c(z) = a(z) * b(z) mod f(z) + + // No check performed here for performance reasons. Instead the + // elements involved are checked in ECPoint.F2m + // checkFieldElements(this, b); + F2m bF2m = (F2m)b; + IntArray mult = x.multiply(bF2m.x, m); + mult.reduce(m, new int[]{k1, k2, k3}); + return new F2m(m, k1, k2, k3, mult); + } + + public ECFieldElement divide(final ECFieldElement b) + { + // There may be more efficient implementations + ECFieldElement bInv = b.invert(); + return multiply(bInv); + } + + public ECFieldElement negate() + { + // -x == x holds for all x in F2m + return this; + } + + public ECFieldElement square() + { + IntArray squared = x.square(m); + squared.reduce(m, new int[]{k1, k2, k3}); + return new F2m(m, k1, k2, k3, squared); + } + + + public ECFieldElement invert() + { + // Inversion in F2m using the extended Euclidean algorithm + // Input: A nonzero polynomial a(z) of degree at most m-1 + // Output: a(z)^(-1) mod f(z) + + // u(z) := a(z) + IntArray uz = (IntArray)this.x.clone(); + + // v(z) := f(z) + IntArray vz = new IntArray(t); + vz.setBit(m); + vz.setBit(0); + vz.setBit(this.k1); + if (this.representation == PPB) + { + vz.setBit(this.k2); + vz.setBit(this.k3); + } + + // g1(z) := 1, g2(z) := 0 + IntArray g1z = new IntArray(t); + g1z.setBit(0); + IntArray g2z = new IntArray(t); + + // while u != 0 + while (!uz.isZero()) +// while (uz.getUsedLength() > 0) +// while (uz.bitLength() > 1) + { + // j := deg(u(z)) - deg(v(z)) + int j = uz.bitLength() - vz.bitLength(); + + // If j < 0 then: u(z) <-> v(z), g1(z) <-> g2(z), j := -j + if (j < 0) + { + final IntArray uzCopy = uz; + uz = vz; + vz = uzCopy; + + final IntArray g1zCopy = g1z; + g1z = g2z; + g2z = g1zCopy; + + j = -j; + } + + // u(z) := u(z) + z^j * v(z) + // Note, that no reduction modulo f(z) is required, because + // deg(u(z) + z^j * v(z)) <= max(deg(u(z)), j + deg(v(z))) + // = max(deg(u(z)), deg(u(z)) - deg(v(z)) + deg(v(z)) + // = deg(u(z)) + // uz = uz.xor(vz.shiftLeft(j)); + // jInt = n / 32 + int jInt = j >> 5; + // jInt = n % 32 + int jBit = j & 0x1F; + IntArray vzShift = vz.shiftLeft(jBit); + uz.addShifted(vzShift, jInt); + + // g1(z) := g1(z) + z^j * g2(z) +// g1z = g1z.xor(g2z.shiftLeft(j)); + IntArray g2zShift = g2z.shiftLeft(jBit); + g1z.addShifted(g2zShift, jInt); + + } + return new ECFieldElement.F2m( + this.m, this.k1, this.k2, this.k3, g2z); + } + + public ECFieldElement sqrt() + { + throw new RuntimeException("Not implemented"); + } + + /** + * @return the representation of the field + * F2m, either of + * TPB (trinomial + * basis representation) or + * PPB (pentanomial + * basis representation). + */ + public int getRepresentation() + { + return this.representation; + } + + /** + * @return the degree m of the reduction polynomial + * f(z). + */ + public int getM() + { + return this.m; + } + + /** + * @return TPB: The integer k where xm + + * xk + 1 represents the reduction polynomial + * f(z).
    + * PPB: The integer k1 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z).
    + */ + public int getK1() + { + return this.k1; + } + + /** + * @return TPB: Always returns 0
    + * PPB: The integer k2 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z).
    + */ + public int getK2() + { + return this.k2; + } + + /** + * @return TPB: Always set to 0
    + * PPB: The integer k3 where xm + + * xk3 + xk2 + xk1 + 1 + * represents the reduction polynomial f(z).
    + */ + public int getK3() + { + return this.k3; + } + + public boolean equals(Object anObject) + { + if (anObject == this) + { + return true; + } + + if (!(anObject instanceof ECFieldElement.F2m)) + { + return false; + } + + ECFieldElement.F2m b = (ECFieldElement.F2m)anObject; + + return ((this.m == b.m) && (this.k1 == b.k1) && (this.k2 == b.k2) + && (this.k3 == b.k3) + && (this.representation == b.representation) + && (this.x.equals(b.x))); + } + + public int hashCode() + { + return x.hashCode() ^ m ^ k1 ^ k2 ^ k3; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/ECMultiplier.java b/src/com/google/bitcoin/bouncycastle/math/ec/ECMultiplier.java new file mode 100644 index 000000000..88b9cc751 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/ECMultiplier.java @@ -0,0 +1,19 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +import java.math.BigInteger; + +/** + * Interface for classes encapsulating a point multiplication algorithm + * for ECPoints. + */ +interface ECMultiplier +{ + /** + * Multiplies the ECPoint p by k, i.e. + * p is added k times to itself. + * @param p The ECPoint to be multiplied. + * @param k The factor by which p i multiplied. + * @return p multiplied by k. + */ + ECPoint multiply(ECPoint p, BigInteger k, PreCompInfo preCompInfo); +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/ECPoint.java b/src/com/google/bitcoin/bouncycastle/math/ec/ECPoint.java new file mode 100644 index 000000000..463964550 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/ECPoint.java @@ -0,0 +1,594 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +import java.math.BigInteger; + +import com.google.bitcoin.bouncycastle.asn1.x9.X9IntegerConverter; + +/** + * base class for points on elliptic curves. + */ +public abstract class ECPoint +{ + ECCurve curve; + ECFieldElement x; + ECFieldElement y; + + protected boolean withCompression; + + protected ECMultiplier multiplier = null; + + protected PreCompInfo preCompInfo = null; + + private static X9IntegerConverter converter = new X9IntegerConverter(); + + protected ECPoint(ECCurve curve, ECFieldElement x, ECFieldElement y) + { + this.curve = curve; + this.x = x; + this.y = y; + } + + public ECCurve getCurve() + { + return curve; + } + + public ECFieldElement getX() + { + return x; + } + + public ECFieldElement getY() + { + return y; + } + + public boolean isInfinity() + { + return x == null && y == null; + } + + public boolean isCompressed() + { + return withCompression; + } + + public boolean equals( + Object other) + { + if (other == this) + { + return true; + } + + if (!(other instanceof ECPoint)) + { + return false; + } + + ECPoint o = (ECPoint)other; + + if (this.isInfinity()) + { + return o.isInfinity(); + } + + return x.equals(o.x) && y.equals(o.y); + } + + public int hashCode() + { + if (this.isInfinity()) + { + return 0; + } + + return x.hashCode() ^ y.hashCode(); + } + +// /** +// * Mainly for testing. Explicitly set the ECMultiplier. +// * @param multiplier The ECMultiplier to be used to multiply +// * this ECPoint. +// */ +// public void setECMultiplier(ECMultiplier multiplier) +// { +// this.multiplier = multiplier; +// } + + /** + * Sets the PreCompInfo. Used by ECMultipliers + * to save the precomputation for this ECPoint to store the + * precomputation result for use by subsequent multiplication. + * @param preCompInfo The values precomputed by the + * ECMultiplier. + */ + void setPreCompInfo(PreCompInfo preCompInfo) + { + this.preCompInfo = preCompInfo; + } + + public abstract byte[] getEncoded(); + + public abstract ECPoint add(ECPoint b); + public abstract ECPoint subtract(ECPoint b); + public abstract ECPoint negate(); + public abstract ECPoint twice(); + + /** + * Sets the default ECMultiplier, unless already set. + */ + synchronized void assertECMultiplier() + { + if (this.multiplier == null) + { + this.multiplier = new FpNafMultiplier(); + } + } + + /** + * Multiplies this ECPoint by the given number. + * @param k The multiplicator. + * @return k * this. + */ + public ECPoint multiply(BigInteger k) + { + if (this.isInfinity()) + { + return this; + } + + if (k.signum() == 0) + { + return this.curve.getInfinity(); + } + + assertECMultiplier(); + return this.multiplier.multiply(this, k, preCompInfo); + } + + /** + * Elliptic curve points over Fp + */ + public static class Fp extends ECPoint + { + + /** + * Create a point which encodes with point compression. + * + * @param curve the curve to use + * @param x affine x co-ordinate + * @param y affine y co-ordinate + */ + public Fp(ECCurve curve, ECFieldElement x, ECFieldElement y) + { + this(curve, x, y, false); + } + + /** + * Create a point that encodes with or without point compresion. + * + * @param curve the curve to use + * @param x affine x co-ordinate + * @param y affine y co-ordinate + * @param withCompression if true encode with point compression + */ + public Fp(ECCurve curve, ECFieldElement x, ECFieldElement y, boolean withCompression) + { + super(curve, x, y); + + if ((x != null && y == null) || (x == null && y != null)) + { + throw new IllegalArgumentException("Exactly one of the field elements is null"); + } + + this.withCompression = withCompression; + } + + /** + * return the field element encoded with point compression. (S 4.3.6) + */ + public byte[] getEncoded() + { + if (this.isInfinity()) + { + return new byte[1]; + } + + int qLength = converter.getByteLength(x); + + if (withCompression) + { + byte PC; + + if (this.getY().toBigInteger().testBit(0)) + { + PC = 0x03; + } + else + { + PC = 0x02; + } + + byte[] X = converter.integerToBytes(this.getX().toBigInteger(), qLength); + byte[] PO = new byte[X.length + 1]; + + PO[0] = PC; + System.arraycopy(X, 0, PO, 1, X.length); + + return PO; + } + else + { + byte[] X = converter.integerToBytes(this.getX().toBigInteger(), qLength); + byte[] Y = converter.integerToBytes(this.getY().toBigInteger(), qLength); + byte[] PO = new byte[X.length + Y.length + 1]; + + PO[0] = 0x04; + System.arraycopy(X, 0, PO, 1, X.length); + System.arraycopy(Y, 0, PO, X.length + 1, Y.length); + + return PO; + } + } + + // B.3 pg 62 + public ECPoint add(ECPoint b) + { + if (this.isInfinity()) + { + return b; + } + + if (b.isInfinity()) + { + return this; + } + + // Check if b = this or b = -this + if (this.x.equals(b.x)) + { + if (this.y.equals(b.y)) + { + // this = b, i.e. this must be doubled + return this.twice(); + } + + // this = -b, i.e. the result is the point at infinity + return this.curve.getInfinity(); + } + + ECFieldElement gamma = b.y.subtract(this.y).divide(b.x.subtract(this.x)); + + ECFieldElement x3 = gamma.square().subtract(this.x).subtract(b.x); + ECFieldElement y3 = gamma.multiply(this.x.subtract(x3)).subtract(this.y); + + return new ECPoint.Fp(curve, x3, y3); + } + + // B.3 pg 62 + public ECPoint twice() + { + if (this.isInfinity()) + { + // Twice identity element (point at infinity) is identity + return this; + } + + if (this.y.toBigInteger().signum() == 0) + { + // if y1 == 0, then (x1, y1) == (x1, -y1) + // and hence this = -this and thus 2(x1, y1) == infinity + return this.curve.getInfinity(); + } + + ECFieldElement TWO = this.curve.fromBigInteger(BigInteger.valueOf(2)); + ECFieldElement THREE = this.curve.fromBigInteger(BigInteger.valueOf(3)); + ECFieldElement gamma = this.x.square().multiply(THREE).add(curve.a).divide(y.multiply(TWO)); + + ECFieldElement x3 = gamma.square().subtract(this.x.multiply(TWO)); + ECFieldElement y3 = gamma.multiply(this.x.subtract(x3)).subtract(this.y); + + return new ECPoint.Fp(curve, x3, y3, this.withCompression); + } + + // D.3.2 pg 102 (see Note:) + public ECPoint subtract(ECPoint b) + { + if (b.isInfinity()) + { + return this; + } + + // Add -b + return add(b.negate()); + } + + public ECPoint negate() + { + return new ECPoint.Fp(curve, this.x, this.y.negate(), this.withCompression); + } + + // TODO Uncomment this to enable WNAF algorithm for Fp point multiplication +// /** +// * Sets the default ECMultiplier, unless already set. +// */ +// synchronized void assertECMultiplier() +// { +// if (this.multiplier == null) +// { +// this.multiplier = new WNafMultiplier(); +// } +// } + } + + /** + * Elliptic curve points over F2m + */ + public static class F2m extends ECPoint + { + /** + * @param curve base curve + * @param x x point + * @param y y point + */ + public F2m(ECCurve curve, ECFieldElement x, ECFieldElement y) + { + this(curve, x, y, false); + } + + /** + * @param curve base curve + * @param x x point + * @param y y point + * @param withCompression true if encode with point compression. + */ + public F2m(ECCurve curve, ECFieldElement x, ECFieldElement y, boolean withCompression) + { + super(curve, x, y); + + if ((x != null && y == null) || (x == null && y != null)) + { + throw new IllegalArgumentException("Exactly one of the field elements is null"); + } + + if (x != null) + { + // Check if x and y are elements of the same field + ECFieldElement.F2m.checkFieldElements(this.x, this.y); + + // Check if x and a are elements of the same field + if (curve != null) + { + ECFieldElement.F2m.checkFieldElements(this.x, this.curve.getA()); + } + } + + this.withCompression = withCompression; + } + + /** + * @deprecated use ECCurve.getInfinity() + * Constructor for point at infinity + */ + public F2m(ECCurve curve) + { + super(curve, null, null); + } + + /* (non-Javadoc) + * @see org.bouncycastle.math.ec.ECPoint#getEncoded() + */ + public byte[] getEncoded() + { + if (this.isInfinity()) + { + return new byte[1]; + } + + int byteCount = converter.getByteLength(this.x); + byte[] X = converter.integerToBytes(this.getX().toBigInteger(), byteCount); + byte[] PO; + + if (withCompression) + { + // See X9.62 4.3.6 and 4.2.2 + PO = new byte[byteCount + 1]; + + PO[0] = 0x02; + // X9.62 4.2.2 and 4.3.6: + // if x = 0 then ypTilde := 0, else ypTilde is the rightmost + // bit of y * x^(-1) + // if ypTilde = 0, then PC := 02, else PC := 03 + // Note: PC === PO[0] + if (!(this.getX().toBigInteger().equals(ECConstants.ZERO))) + { + if (this.getY().multiply(this.getX().invert()) + .toBigInteger().testBit(0)) + { + // ypTilde = 1, hence PC = 03 + PO[0] = 0x03; + } + } + + System.arraycopy(X, 0, PO, 1, byteCount); + } + else + { + byte[] Y = converter.integerToBytes(this.getY().toBigInteger(), byteCount); + + PO = new byte[byteCount + byteCount + 1]; + + PO[0] = 0x04; + System.arraycopy(X, 0, PO, 1, byteCount); + System.arraycopy(Y, 0, PO, byteCount + 1, byteCount); + } + + return PO; + } + + /** + * Check, if two ECPoints can be added or subtracted. + * @param a The first ECPoint to check. + * @param b The second ECPoint to check. + * @throws IllegalArgumentException if a and b + * cannot be added. + */ + private static void checkPoints(ECPoint a, ECPoint b) + { + // Check, if points are on the same curve + if (!(a.curve.equals(b.curve))) + { + throw new IllegalArgumentException("Only points on the same " + + "curve can be added or subtracted"); + } + +// ECFieldElement.F2m.checkFieldElements(a.x, b.x); + } + + /* (non-Javadoc) + * @see org.bouncycastle.math.ec.ECPoint#add(org.bouncycastle.math.ec.ECPoint) + */ + public ECPoint add(ECPoint b) + { + checkPoints(this, b); + return addSimple((ECPoint.F2m)b); + } + + /** + * Adds another ECPoints.F2m to this without + * checking if both points are on the same curve. Used by multiplication + * algorithms, because there all points are a multiple of the same point + * and hence the checks can be omitted. + * @param b The other ECPoints.F2m to add to + * this. + * @return this + b + */ + public ECPoint.F2m addSimple(ECPoint.F2m b) + { + ECPoint.F2m other = b; + if (this.isInfinity()) + { + return other; + } + + if (other.isInfinity()) + { + return this; + } + + ECFieldElement.F2m x2 = (ECFieldElement.F2m)other.getX(); + ECFieldElement.F2m y2 = (ECFieldElement.F2m)other.getY(); + + // Check if other = this or other = -this + if (this.x.equals(x2)) + { + if (this.y.equals(y2)) + { + // this = other, i.e. this must be doubled + return (ECPoint.F2m)this.twice(); + } + + // this = -other, i.e. the result is the point at infinity + return (ECPoint.F2m)this.curve.getInfinity(); + } + + ECFieldElement.F2m lambda + = (ECFieldElement.F2m)(this.y.add(y2)).divide(this.x.add(x2)); + + ECFieldElement.F2m x3 + = (ECFieldElement.F2m)lambda.square().add(lambda).add(this.x).add(x2).add(this.curve.getA()); + + ECFieldElement.F2m y3 + = (ECFieldElement.F2m)lambda.multiply(this.x.add(x3)).add(x3).add(this.y); + + return new ECPoint.F2m(curve, x3, y3, withCompression); + } + + /* (non-Javadoc) + * @see org.bouncycastle.math.ec.ECPoint#subtract(org.bouncycastle.math.ec.ECPoint) + */ + public ECPoint subtract(ECPoint b) + { + checkPoints(this, b); + return subtractSimple((ECPoint.F2m)b); + } + + /** + * Subtracts another ECPoints.F2m from this + * without checking if both points are on the same curve. Used by + * multiplication algorithms, because there all points are a multiple + * of the same point and hence the checks can be omitted. + * @param b The other ECPoints.F2m to subtract from + * this. + * @return this - b + */ + public ECPoint.F2m subtractSimple(ECPoint.F2m b) + { + if (b.isInfinity()) + { + return this; + } + + // Add -b + return addSimple((ECPoint.F2m)b.negate()); + } + + /* (non-Javadoc) + * @see org.bouncycastle.math.ec.ECPoint#twice() + */ + public ECPoint twice() + { + if (this.isInfinity()) + { + // Twice identity element (point at infinity) is identity + return this; + } + + if (this.x.toBigInteger().signum() == 0) + { + // if x1 == 0, then (x1, y1) == (x1, x1 + y1) + // and hence this = -this and thus 2(x1, y1) == infinity + return this.curve.getInfinity(); + } + + ECFieldElement.F2m lambda + = (ECFieldElement.F2m)this.x.add(this.y.divide(this.x)); + + ECFieldElement.F2m x3 + = (ECFieldElement.F2m)lambda.square().add(lambda). + add(this.curve.getA()); + + ECFieldElement ONE = this.curve.fromBigInteger(ECConstants.ONE); + ECFieldElement.F2m y3 + = (ECFieldElement.F2m)this.x.square().add( + x3.multiply(lambda.add(ONE))); + + return new ECPoint.F2m(this.curve, x3, y3, withCompression); + } + + public ECPoint negate() + { + return new ECPoint.F2m(curve, this.getX(), this.getY().add(this.getX()), withCompression); + } + + // TODO Uncomment this to enable WNAF/WTNAF F2m point multiplication +// /** +// * Sets the appropriate ECMultiplier, unless already set. +// */ +// synchronized void assertECMultiplier() +// { +// if (this.multiplier == null) +// { +// if (((ECCurve.F2m)(this.curve)).isKoblitz()) +// { +// this.multiplier = new WTauNafMultiplier(); +// } +// else +// { +// this.multiplier = new WNafMultiplier(); +// } +// } +// } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/FpNafMultiplier.java b/src/com/google/bitcoin/bouncycastle/math/ec/FpNafMultiplier.java new file mode 100644 index 000000000..5e3a6d6d9 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/FpNafMultiplier.java @@ -0,0 +1,39 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +import java.math.BigInteger; + +/** + * Class implementing the NAF (Non-Adjacent Form) multiplication algorithm. + */ +class FpNafMultiplier implements ECMultiplier +{ + /** + * D.3.2 pg 101 + * @see com.google.bitcoin.bouncycastle.math.ec.ECMultiplier#multiply(com.google.bitcoin.bouncycastle.math.ec.ECPoint, java.math.BigInteger) + */ + public ECPoint multiply(ECPoint p, BigInteger k, PreCompInfo preCompInfo) + { + // TODO Probably should try to add this + // BigInteger e = k.mod(n); // n == order of p + BigInteger e = k; + BigInteger h = e.multiply(BigInteger.valueOf(3)); + + ECPoint neg = p.negate(); + ECPoint R = p; + + for (int i = h.bitLength() - 2; i > 0; --i) + { + R = R.twice(); + + boolean hBit = h.testBit(i); + boolean eBit = e.testBit(i); + + if (hBit != eBit) + { + R = R.add(hBit ? p : neg); + } + } + + return R; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/IntArray.java b/src/com/google/bitcoin/bouncycastle/math/ec/IntArray.java new file mode 100644 index 000000000..1ef37dd1b --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/IntArray.java @@ -0,0 +1,518 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +import com.google.bitcoin.bouncycastle.util.Arrays; + +import java.math.BigInteger; + +class IntArray +{ + // TODO make m fixed for the IntArray, and hence compute T once and for all + + private int[] m_ints; + + public IntArray(int intLen) + { + m_ints = new int[intLen]; + } + + public IntArray(int[] ints) + { + m_ints = ints; + } + + public IntArray(BigInteger bigInt) + { + this(bigInt, 0); + } + + public IntArray(BigInteger bigInt, int minIntLen) + { + if (bigInt.signum() == -1) + { + throw new IllegalArgumentException("Only positive Integers allowed"); + } + if (bigInt.equals(ECConstants.ZERO)) + { + m_ints = new int[] { 0 }; + return; + } + + byte[] barr = bigInt.toByteArray(); + int barrLen = barr.length; + int barrStart = 0; + if (barr[0] == 0) + { + // First byte is 0 to enforce highest (=sign) bit is zero. + // In this case ignore barr[0]. + barrLen--; + barrStart = 1; + } + int intLen = (barrLen + 3) / 4; + if (intLen < minIntLen) + { + m_ints = new int[minIntLen]; + } + else + { + m_ints = new int[intLen]; + } + + int iarrJ = intLen - 1; + int rem = barrLen % 4 + barrStart; + int temp = 0; + int barrI = barrStart; + if (barrStart < rem) + { + for (; barrI < rem; barrI++) + { + temp <<= 8; + int barrBarrI = barr[barrI]; + if (barrBarrI < 0) + { + barrBarrI += 256; + } + temp |= barrBarrI; + } + m_ints[iarrJ--] = temp; + } + + for (; iarrJ >= 0; iarrJ--) + { + temp = 0; + for (int i = 0; i < 4; i++) + { + temp <<= 8; + int barrBarrI = barr[barrI++]; + if (barrBarrI < 0) + { + barrBarrI += 256; + } + temp |= barrBarrI; + } + m_ints[iarrJ] = temp; + } + } + + public boolean isZero() + { + return m_ints.length == 0 + || (m_ints[0] == 0 && getUsedLength() == 0); + } + + public int getUsedLength() + { + int highestIntPos = m_ints.length; + + if (highestIntPos < 1) + { + return 0; + } + + // Check if first element will act as sentinel + if (m_ints[0] != 0) + { + while (m_ints[--highestIntPos] == 0) + { + } + return highestIntPos + 1; + } + + do + { + if (m_ints[--highestIntPos] != 0) + { + return highestIntPos + 1; + } + } + while (highestIntPos > 0); + + return 0; + } + + public int bitLength() + { + // JDK 1.5: see Integer.numberOfLeadingZeros() + int intLen = getUsedLength(); + if (intLen == 0) + { + return 0; + } + + int last = intLen - 1; + int highest = m_ints[last]; + int bits = (last << 5) + 1; + + // A couple of binary search steps + if ((highest & 0xffff0000) != 0) + { + if ((highest & 0xff000000) != 0) + { + bits += 24; + highest >>>= 24; + } + else + { + bits += 16; + highest >>>= 16; + } + } + else if (highest > 0x000000ff) + { + bits += 8; + highest >>>= 8; + } + + while (highest != 1) + { + ++bits; + highest >>>= 1; + } + + return bits; + } + + private int[] resizedInts(int newLen) + { + int[] newInts = new int[newLen]; + int oldLen = m_ints.length; + int copyLen = oldLen < newLen ? oldLen : newLen; + System.arraycopy(m_ints, 0, newInts, 0, copyLen); + return newInts; + } + + public BigInteger toBigInteger() + { + int usedLen = getUsedLength(); + if (usedLen == 0) + { + return ECConstants.ZERO; + } + + int highestInt = m_ints[usedLen - 1]; + byte[] temp = new byte[4]; + int barrI = 0; + boolean trailingZeroBytesDone = false; + for (int j = 3; j >= 0; j--) + { + byte thisByte = (byte) (highestInt >>> (8 * j)); + if (trailingZeroBytesDone || (thisByte != 0)) + { + trailingZeroBytesDone = true; + temp[barrI++] = thisByte; + } + } + + int barrLen = 4 * (usedLen - 1) + barrI; + byte[] barr = new byte[barrLen]; + for (int j = 0; j < barrI; j++) + { + barr[j] = temp[j]; + } + // Highest value int is done now + + for (int iarrJ = usedLen - 2; iarrJ >= 0; iarrJ--) + { + for (int j = 3; j >= 0; j--) + { + barr[barrI++] = (byte) (m_ints[iarrJ] >>> (8 * j)); + } + } + return new BigInteger(1, barr); + } + + public void shiftLeft() + { + int usedLen = getUsedLength(); + if (usedLen == 0) + { + return; + } + if (m_ints[usedLen - 1] < 0) + { + // highest bit of highest used byte is set, so shifting left will + // make the IntArray one byte longer + usedLen++; + if (usedLen > m_ints.length) + { + // make the m_ints one byte longer, because we need one more + // byte which is not available in m_ints + m_ints = resizedInts(m_ints.length + 1); + } + } + + boolean carry = false; + for (int i = 0; i < usedLen; i++) + { + // nextCarry is true if highest bit is set + boolean nextCarry = m_ints[i] < 0; + m_ints[i] <<= 1; + if (carry) + { + // set lowest bit + m_ints[i] |= 1; + } + carry = nextCarry; + } + } + + public IntArray shiftLeft(int n) + { + int usedLen = getUsedLength(); + if (usedLen == 0) + { + return this; + } + + if (n == 0) + { + return this; + } + + if (n > 31) + { + throw new IllegalArgumentException("shiftLeft() for max 31 bits " + + ", " + n + "bit shift is not possible"); + } + + int[] newInts = new int[usedLen + 1]; + + int nm32 = 32 - n; + newInts[0] = m_ints[0] << n; + for (int i = 1; i < usedLen; i++) + { + newInts[i] = (m_ints[i] << n) | (m_ints[i - 1] >>> nm32); + } + newInts[usedLen] = m_ints[usedLen - 1] >>> nm32; + + return new IntArray(newInts); + } + + public void addShifted(IntArray other, int shift) + { + int usedLenOther = other.getUsedLength(); + int newMinUsedLen = usedLenOther + shift; + if (newMinUsedLen > m_ints.length) + { + m_ints = resizedInts(newMinUsedLen); + //System.out.println("Resize required"); + } + + for (int i = 0; i < usedLenOther; i++) + { + m_ints[i + shift] ^= other.m_ints[i]; + } + } + + public int getLength() + { + return m_ints.length; + } + + public boolean testBit(int n) + { + // theInt = n / 32 + int theInt = n >> 5; + // theBit = n % 32 + int theBit = n & 0x1F; + int tester = 1 << theBit; + return ((m_ints[theInt] & tester) != 0); + } + + public void flipBit(int n) + { + // theInt = n / 32 + int theInt = n >> 5; + // theBit = n % 32 + int theBit = n & 0x1F; + int flipper = 1 << theBit; + m_ints[theInt] ^= flipper; + } + + public void setBit(int n) + { + // theInt = n / 32 + int theInt = n >> 5; + // theBit = n % 32 + int theBit = n & 0x1F; + int setter = 1 << theBit; + m_ints[theInt] |= setter; + } + + public IntArray multiply(IntArray other, int m) + { + // Lenght of c is 2m bits rounded up to the next int (32 bit) + int t = (m + 31) >> 5; + if (m_ints.length < t) + { + m_ints = resizedInts(t); + } + + IntArray b = new IntArray(other.resizedInts(other.getLength() + 1)); + IntArray c = new IntArray((m + m + 31) >> 5); + // IntArray c = new IntArray(t + t); + int testBit = 1; + for (int k = 0; k < 32; k++) + { + for (int j = 0; j < t; j++) + { + if ((m_ints[j] & testBit) != 0) + { + // The kth bit of m_ints[j] is set + c.addShifted(b, j); + } + } + testBit <<= 1; + b.shiftLeft(); + } + return c; + } + + // public IntArray multiplyLeftToRight(IntArray other, int m) { + // // Lenght of c is 2m bits rounded up to the next int (32 bit) + // int t = (m + 31) / 32; + // if (m_ints.length < t) { + // m_ints = resizedInts(t); + // } + // + // IntArray b = new IntArray(other.resizedInts(other.getLength() + 1)); + // IntArray c = new IntArray((m + m + 31) / 32); + // // IntArray c = new IntArray(t + t); + // int testBit = 1 << 31; + // for (int k = 31; k >= 0; k--) { + // for (int j = 0; j < t; j++) { + // if ((m_ints[j] & testBit) != 0) { + // // The kth bit of m_ints[j] is set + // c.addShifted(b, j); + // } + // } + // testBit >>>= 1; + // if (k > 0) { + // c.shiftLeft(); + // } + // } + // return c; + // } + + // TODO note, redPol.length must be 3 for TPB and 5 for PPB + public void reduce(int m, int[] redPol) + { + for (int i = m + m - 2; i >= m; i--) + { + if (testBit(i)) + { + int bit = i - m; + flipBit(bit); + flipBit(i); + int l = redPol.length; + while (--l >= 0) + { + flipBit(redPol[l] + bit); + } + } + } + m_ints = resizedInts((m + 31) >> 5); + } + + public IntArray square(int m) + { + // TODO make the table static final + final int[] table = { 0x0, 0x1, 0x4, 0x5, 0x10, 0x11, 0x14, 0x15, 0x40, + 0x41, 0x44, 0x45, 0x50, 0x51, 0x54, 0x55 }; + + int t = (m + 31) >> 5; + if (m_ints.length < t) + { + m_ints = resizedInts(t); + } + + IntArray c = new IntArray(t + t); + + // TODO twice the same code, put in separate private method + for (int i = 0; i < t; i++) + { + int v0 = 0; + for (int j = 0; j < 4; j++) + { + v0 = v0 >>> 8; + int u = (m_ints[i] >>> (j * 4)) & 0xF; + int w = table[u] << 24; + v0 |= w; + } + c.m_ints[i + i] = v0; + + v0 = 0; + int upper = m_ints[i] >>> 16; + for (int j = 0; j < 4; j++) + { + v0 = v0 >>> 8; + int u = (upper >>> (j * 4)) & 0xF; + int w = table[u] << 24; + v0 |= w; + } + c.m_ints[i + i + 1] = v0; + } + return c; + } + + public boolean equals(Object o) + { + if (!(o instanceof IntArray)) + { + return false; + } + IntArray other = (IntArray) o; + int usedLen = getUsedLength(); + if (other.getUsedLength() != usedLen) + { + return false; + } + for (int i = 0; i < usedLen; i++) + { + if (m_ints[i] != other.m_ints[i]) + { + return false; + } + } + return true; + } + + public int hashCode() + { + int usedLen = getUsedLength(); + int hash = 1; + for (int i = 0; i < usedLen; i++) + { + hash = hash * 31 + m_ints[i]; + } + return hash; + } + + public Object clone() + { + return new IntArray(Arrays.clone(m_ints)); + } + + public String toString() + { + int usedLen = getUsedLength(); + if (usedLen == 0) + { + return "0"; + } + + StringBuffer sb = new StringBuffer(Integer + .toBinaryString(m_ints[usedLen - 1])); + for (int iarrJ = usedLen - 2; iarrJ >= 0; iarrJ--) + { + String hexString = Integer.toBinaryString(m_ints[iarrJ]); + + // Add leading zeroes, except for highest significant int + for (int i = hexString.length(); i < 8; i++) + { + hexString = "0" + hexString; + } + sb.append(hexString); + } + return sb.toString(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/PreCompInfo.java b/src/com/google/bitcoin/bouncycastle/math/ec/PreCompInfo.java new file mode 100644 index 000000000..de88e6af3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/PreCompInfo.java @@ -0,0 +1,10 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +/** + * Interface for classes storing precomputation data for multiplication + * algorithms. Used as a Memento (see GOF patterns) for + * WNafMultiplier. + */ +interface PreCompInfo +{ +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/ReferenceMultiplier.java b/src/com/google/bitcoin/bouncycastle/math/ec/ReferenceMultiplier.java new file mode 100644 index 000000000..162d1d99e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/ReferenceMultiplier.java @@ -0,0 +1,30 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +import java.math.BigInteger; + +class ReferenceMultiplier implements ECMultiplier +{ + /** + * Simple shift-and-add multiplication. Serves as reference implementation + * to verify (possibly faster) implementations in + * {@link com.google.bitcoin.bouncycastle.math.ec.ECPoint ECPoint}. + * + * @param p The point to multiply. + * @param k The factor by which to multiply. + * @return The result of the point multiplication k * p. + */ + public ECPoint multiply(ECPoint p, BigInteger k, PreCompInfo preCompInfo) + { + ECPoint q = p.getCurve().getInfinity(); + int t = k.bitLength(); + for (int i = 0; i < t; i++) + { + if (k.testBit(i)) + { + q = q.add(p); + } + p = p.twice(); + } + return q; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/SimpleBigDecimal.java b/src/com/google/bitcoin/bouncycastle/math/ec/SimpleBigDecimal.java new file mode 100644 index 000000000..9db6d32c3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/SimpleBigDecimal.java @@ -0,0 +1,253 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +import java.math.BigInteger; + +/** + * Class representing a simple version of a big decimal. A + * SimpleBigDecimal is basically a + * {@link java.math.BigInteger BigInteger} with a few digits on the right of + * the decimal point. The number of (binary) digits on the right of the decimal + * point is called the scale of the SimpleBigDecimal. + * Unlike in {@link java.math.BigDecimal BigDecimal}, the scale is not adjusted + * automatically, but must be set manually. All SimpleBigDecimals + * taking part in the same arithmetic operation must have equal scale. The + * result of a multiplication of two SimpleBigDecimals returns a + * SimpleBigDecimal with double scale. + */ +class SimpleBigDecimal + //extends Number // not in J2ME - add compatibility class? +{ + private static final long serialVersionUID = 1L; + + private final BigInteger bigInt; + private final int scale; + + /** + * Returns a SimpleBigDecimal representing the same numerical + * value as value. + * @param value The value of the SimpleBigDecimal to be + * created. + * @param scale The scale of the SimpleBigDecimal to be + * created. + * @return The such created SimpleBigDecimal. + */ + public static SimpleBigDecimal getInstance(BigInteger value, int scale) + { + return new SimpleBigDecimal(value.shiftLeft(scale), scale); + } + + /** + * Constructor for SimpleBigDecimal. The value of the + * constructed SimpleBigDecimal equals bigInt / + * 2scale. + * @param bigInt The bigInt value parameter. + * @param scale The scale of the constructed SimpleBigDecimal. + */ + public SimpleBigDecimal(BigInteger bigInt, int scale) + { + if (scale < 0) + { + throw new IllegalArgumentException("scale may not be negative"); + } + + this.bigInt = bigInt; + this.scale = scale; + } + + private SimpleBigDecimal(SimpleBigDecimal limBigDec) + { + bigInt = limBigDec.bigInt; + scale = limBigDec.scale; + } + + private void checkScale(SimpleBigDecimal b) + { + if (scale != b.scale) + { + throw new IllegalArgumentException("Only SimpleBigDecimal of " + + "same scale allowed in arithmetic operations"); + } + } + + public SimpleBigDecimal adjustScale(int newScale) + { + if (newScale < 0) + { + throw new IllegalArgumentException("scale may not be negative"); + } + + if (newScale == scale) + { + return new SimpleBigDecimal(this); + } + + return new SimpleBigDecimal(bigInt.shiftLeft(newScale - scale), + newScale); + } + + public SimpleBigDecimal add(SimpleBigDecimal b) + { + checkScale(b); + return new SimpleBigDecimal(bigInt.add(b.bigInt), scale); + } + + public SimpleBigDecimal add(BigInteger b) + { + return new SimpleBigDecimal(bigInt.add(b.shiftLeft(scale)), scale); + } + + public SimpleBigDecimal negate() + { + return new SimpleBigDecimal(bigInt.negate(), scale); + } + + public SimpleBigDecimal subtract(SimpleBigDecimal b) + { + return add(b.negate()); + } + + public SimpleBigDecimal subtract(BigInteger b) + { + return new SimpleBigDecimal(bigInt.subtract(b.shiftLeft(scale)), + scale); + } + + public SimpleBigDecimal multiply(SimpleBigDecimal b) + { + checkScale(b); + return new SimpleBigDecimal(bigInt.multiply(b.bigInt), scale + scale); + } + + public SimpleBigDecimal multiply(BigInteger b) + { + return new SimpleBigDecimal(bigInt.multiply(b), scale); + } + + public SimpleBigDecimal divide(SimpleBigDecimal b) + { + checkScale(b); + BigInteger dividend = bigInt.shiftLeft(scale); + return new SimpleBigDecimal(dividend.divide(b.bigInt), scale); + } + + public SimpleBigDecimal divide(BigInteger b) + { + return new SimpleBigDecimal(bigInt.divide(b), scale); + } + + public SimpleBigDecimal shiftLeft(int n) + { + return new SimpleBigDecimal(bigInt.shiftLeft(n), scale); + } + + public int compareTo(SimpleBigDecimal val) + { + checkScale(val); + return bigInt.compareTo(val.bigInt); + } + + public int compareTo(BigInteger val) + { + return bigInt.compareTo(val.shiftLeft(scale)); + } + + public BigInteger floor() + { + return bigInt.shiftRight(scale); + } + + public BigInteger round() + { + SimpleBigDecimal oneHalf = new SimpleBigDecimal(ECConstants.ONE, 1); + return add(oneHalf.adjustScale(scale)).floor(); + } + + public int intValue() + { + return floor().intValue(); + } + + public long longValue() + { + return floor().longValue(); + } + /* NON-J2ME compliant. + public double doubleValue() + { + return Double.valueOf(toString()).doubleValue(); + } + + public float floatValue() + { + return Float.valueOf(toString()).floatValue(); + } + */ + public int getScale() + { + return scale; + } + + public String toString() + { + if (scale == 0) + { + return bigInt.toString(); + } + + BigInteger floorBigInt = floor(); + + BigInteger fract = bigInt.subtract(floorBigInt.shiftLeft(scale)); + if (bigInt.signum() == -1) + { + fract = ECConstants.ONE.shiftLeft(scale).subtract(fract); + } + + if ((floorBigInt.signum() == -1) && (!(fract.equals(ECConstants.ZERO)))) + { + floorBigInt = floorBigInt.add(ECConstants.ONE); + } + String leftOfPoint = floorBigInt.toString(); + + char[] fractCharArr = new char[scale]; + String fractStr = fract.toString(2); + int fractLen = fractStr.length(); + int zeroes = scale - fractLen; + for (int i = 0; i < zeroes; i++) + { + fractCharArr[i] = '0'; + } + for (int j = 0; j < fractLen; j++) + { + fractCharArr[zeroes + j] = fractStr.charAt(j); + } + String rightOfPoint = new String(fractCharArr); + + StringBuffer sb = new StringBuffer(leftOfPoint); + sb.append("."); + sb.append(rightOfPoint); + + return sb.toString(); + } + + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + + if (!(o instanceof SimpleBigDecimal)) + { + return false; + } + + SimpleBigDecimal other = (SimpleBigDecimal)o; + return ((bigInt.equals(other.bigInt)) && (scale == other.scale)); + } + + public int hashCode() + { + return bigInt.hashCode() ^ scale; + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/Tnaf.java b/src/com/google/bitcoin/bouncycastle/math/ec/Tnaf.java new file mode 100644 index 000000000..a04ad2b15 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/Tnaf.java @@ -0,0 +1,844 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +import java.math.BigInteger; + +/** + * Class holding methods for point multiplication based on the window + * τ-adic nonadjacent form (WTNAF). The algorithms are based on the + * paper "Improved Algorithms for Arithmetic on Anomalous Binary Curves" + * by Jerome A. Solinas. The paper first appeared in the Proceedings of + * Crypto 1997. + */ +class Tnaf +{ + private static final BigInteger MINUS_ONE = ECConstants.ONE.negate(); + private static final BigInteger MINUS_TWO = ECConstants.TWO.negate(); + private static final BigInteger MINUS_THREE = ECConstants.THREE.negate(); + + /** + * The window width of WTNAF. The standard value of 4 is slightly less + * than optimal for running time, but keeps space requirements for + * precomputation low. For typical curves, a value of 5 or 6 results in + * a better running time. When changing this value, the + * αu's must be computed differently, see + * e.g. "Guide to Elliptic Curve Cryptography", Darrel Hankerson, + * Alfred Menezes, Scott Vanstone, Springer-Verlag New York Inc., 2004, + * p. 121-122 + */ + public static final byte WIDTH = 4; + + /** + * 24 + */ + public static final byte POW_2_WIDTH = 16; + + /** + * The αu's for a=0 as an array + * of ZTauElements. + */ + public static final ZTauElement[] alpha0 = { + null, + new ZTauElement(ECConstants.ONE, ECConstants.ZERO), null, + new ZTauElement(MINUS_THREE, MINUS_ONE), null, + new ZTauElement(MINUS_ONE, MINUS_ONE), null, + new ZTauElement(ECConstants.ONE, MINUS_ONE), null + }; + + /** + * The αu's for a=0 as an array + * of TNAFs. + */ + public static final byte[][] alpha0Tnaf = { + null, {1}, null, {-1, 0, 1}, null, {1, 0, 1}, null, {-1, 0, 0, 1} + }; + + /** + * The αu's for a=1 as an array + * of ZTauElements. + */ + public static final ZTauElement[] alpha1 = {null, + new ZTauElement(ECConstants.ONE, ECConstants.ZERO), null, + new ZTauElement(MINUS_THREE, ECConstants.ONE), null, + new ZTauElement(MINUS_ONE, ECConstants.ONE), null, + new ZTauElement(ECConstants.ONE, ECConstants.ONE), null + }; + + /** + * The αu's for a=1 as an array + * of TNAFs. + */ + public static final byte[][] alpha1Tnaf = { + null, {1}, null, {-1, 0, 1}, null, {1, 0, 1}, null, {-1, 0, 0, -1} + }; + + /** + * Computes the norm of an element λ of + * Z[τ]. + * @param mu The parameter μ of the elliptic curve. + * @param lambda The element λ of + * Z[τ]. + * @return The norm of λ. + */ + public static BigInteger norm(final byte mu, ZTauElement lambda) + { + BigInteger norm; + + // s1 = u^2 + BigInteger s1 = lambda.u.multiply(lambda.u); + + // s2 = u * v + BigInteger s2 = lambda.u.multiply(lambda.v); + + // s3 = 2 * v^2 + BigInteger s3 = lambda.v.multiply(lambda.v).shiftLeft(1); + + if (mu == 1) + { + norm = s1.add(s2).add(s3); + } + else if (mu == -1) + { + norm = s1.subtract(s2).add(s3); + } + else + { + throw new IllegalArgumentException("mu must be 1 or -1"); + } + + return norm; + } + + /** + * Computes the norm of an element λ of + * R[τ], where λ = u + vτ + * and u and u are real numbers (elements of + * R). + * @param mu The parameter μ of the elliptic curve. + * @param u The real part of the element λ of + * R[τ]. + * @param v The τ-adic part of the element + * λ of R[τ]. + * @return The norm of λ. + */ + public static SimpleBigDecimal norm(final byte mu, SimpleBigDecimal u, + SimpleBigDecimal v) + { + SimpleBigDecimal norm; + + // s1 = u^2 + SimpleBigDecimal s1 = u.multiply(u); + + // s2 = u * v + SimpleBigDecimal s2 = u.multiply(v); + + // s3 = 2 * v^2 + SimpleBigDecimal s3 = v.multiply(v).shiftLeft(1); + + if (mu == 1) + { + norm = s1.add(s2).add(s3); + } + else if (mu == -1) + { + norm = s1.subtract(s2).add(s3); + } + else + { + throw new IllegalArgumentException("mu must be 1 or -1"); + } + + return norm; + } + + /** + * Rounds an element λ of R[τ] + * to an element of Z[τ], such that their difference + * has minimal norm. λ is given as + * λ = λ0 + λ1τ. + * @param lambda0 The component λ0. + * @param lambda1 The component λ1. + * @param mu The parameter μ of the elliptic curve. Must + * equal 1 or -1. + * @return The rounded element of Z[τ]. + * @throws IllegalArgumentException if lambda0 and + * lambda1 do not have same scale. + */ + public static ZTauElement round(SimpleBigDecimal lambda0, + SimpleBigDecimal lambda1, byte mu) + { + int scale = lambda0.getScale(); + if (lambda1.getScale() != scale) + { + throw new IllegalArgumentException("lambda0 and lambda1 do not " + + "have same scale"); + } + + if (!((mu == 1) || (mu == -1))) + { + throw new IllegalArgumentException("mu must be 1 or -1"); + } + + BigInteger f0 = lambda0.round(); + BigInteger f1 = lambda1.round(); + + SimpleBigDecimal eta0 = lambda0.subtract(f0); + SimpleBigDecimal eta1 = lambda1.subtract(f1); + + // eta = 2*eta0 + mu*eta1 + SimpleBigDecimal eta = eta0.add(eta0); + if (mu == 1) + { + eta = eta.add(eta1); + } + else + { + // mu == -1 + eta = eta.subtract(eta1); + } + + // check1 = eta0 - 3*mu*eta1 + // check2 = eta0 + 4*mu*eta1 + SimpleBigDecimal threeEta1 = eta1.add(eta1).add(eta1); + SimpleBigDecimal fourEta1 = threeEta1.add(eta1); + SimpleBigDecimal check1; + SimpleBigDecimal check2; + if (mu == 1) + { + check1 = eta0.subtract(threeEta1); + check2 = eta0.add(fourEta1); + } + else + { + // mu == -1 + check1 = eta0.add(threeEta1); + check2 = eta0.subtract(fourEta1); + } + + byte h0 = 0; + byte h1 = 0; + + // if eta >= 1 + if (eta.compareTo(ECConstants.ONE) >= 0) + { + if (check1.compareTo(MINUS_ONE) < 0) + { + h1 = mu; + } + else + { + h0 = 1; + } + } + else + { + // eta < 1 + if (check2.compareTo(ECConstants.TWO) >= 0) + { + h1 = mu; + } + } + + // if eta < -1 + if (eta.compareTo(MINUS_ONE) < 0) + { + if (check1.compareTo(ECConstants.ONE) >= 0) + { + h1 = (byte)-mu; + } + else + { + h0 = -1; + } + } + else + { + // eta >= -1 + if (check2.compareTo(MINUS_TWO) < 0) + { + h1 = (byte)-mu; + } + } + + BigInteger q0 = f0.add(BigInteger.valueOf(h0)); + BigInteger q1 = f1.add(BigInteger.valueOf(h1)); + return new ZTauElement(q0, q1); + } + + /** + * Approximate division by n. For an integer + * k, the value λ = s k / n is + * computed to c bits of accuracy. + * @param k The parameter k. + * @param s The curve parameter s0 or + * s1. + * @param vm The Lucas Sequence element Vm. + * @param a The parameter a of the elliptic curve. + * @param m The bit length of the finite field + * Fm. + * @param c The number of bits of accuracy, i.e. the scale of the returned + * SimpleBigDecimal. + * @return The value λ = s k / n computed to + * c bits of accuracy. + */ + public static SimpleBigDecimal approximateDivisionByN(BigInteger k, + BigInteger s, BigInteger vm, byte a, int m, int c) + { + int _k = (m + 5)/2 + c; + BigInteger ns = k.shiftRight(m - _k - 2 + a); + + BigInteger gs = s.multiply(ns); + + BigInteger hs = gs.shiftRight(m); + + BigInteger js = vm.multiply(hs); + + BigInteger gsPlusJs = gs.add(js); + BigInteger ls = gsPlusJs.shiftRight(_k-c); + if (gsPlusJs.testBit(_k-c-1)) + { + // round up + ls = ls.add(ECConstants.ONE); + } + + return new SimpleBigDecimal(ls, c); + } + + /** + * Computes the τ-adic NAF (non-adjacent form) of an + * element λ of Z[τ]. + * @param mu The parameter μ of the elliptic curve. + * @param lambda The element λ of + * Z[τ]. + * @return The τ-adic NAF of λ. + */ + public static byte[] tauAdicNaf(byte mu, ZTauElement lambda) + { + if (!((mu == 1) || (mu == -1))) + { + throw new IllegalArgumentException("mu must be 1 or -1"); + } + + BigInteger norm = norm(mu, lambda); + + // Ceiling of log2 of the norm + int log2Norm = norm.bitLength(); + + // If length(TNAF) > 30, then length(TNAF) < log2Norm + 3.52 + int maxLength = log2Norm > 30 ? log2Norm + 4 : 34; + + // The array holding the TNAF + byte[] u = new byte[maxLength]; + int i = 0; + + // The actual length of the TNAF + int length = 0; + + BigInteger r0 = lambda.u; + BigInteger r1 = lambda.v; + + while(!((r0.equals(ECConstants.ZERO)) && (r1.equals(ECConstants.ZERO)))) + { + // If r0 is odd + if (r0.testBit(0)) + { + u[i] = (byte) ECConstants.TWO.subtract((r0.subtract(r1.shiftLeft(1))).mod(ECConstants.FOUR)).intValue(); + + // r0 = r0 - u[i] + if (u[i] == 1) + { + r0 = r0.clearBit(0); + } + else + { + // u[i] == -1 + r0 = r0.add(ECConstants.ONE); + } + length = i; + } + else + { + u[i] = 0; + } + + BigInteger t = r0; + BigInteger s = r0.shiftRight(1); + if (mu == 1) + { + r0 = r1.add(s); + } + else + { + // mu == -1 + r0 = r1.subtract(s); + } + + r1 = t.shiftRight(1).negate(); + i++; + } + + length++; + + // Reduce the TNAF array to its actual length + byte[] tnaf = new byte[length]; + System.arraycopy(u, 0, tnaf, 0, length); + return tnaf; + } + + /** + * Applies the operation τ() to an + * ECPoint.F2m. + * @param p The ECPoint.F2m to which τ() is applied. + * @return τ(p) + */ + public static ECPoint.F2m tau(ECPoint.F2m p) + { + if (p.isInfinity()) + { + return p; + } + + ECFieldElement x = p.getX(); + ECFieldElement y = p.getY(); + + return new ECPoint.F2m(p.getCurve(), x.square(), y.square(), p.isCompressed()); + } + + /** + * Returns the parameter μ of the elliptic curve. + * @param curve The elliptic curve from which to obtain μ. + * The curve must be a Koblitz curve, i.e. a equals + * 0 or 1 and b equals + * 1. + * @return μ of the elliptic curve. + * @throws IllegalArgumentException if the given ECCurve is not a Koblitz + * curve. + */ + public static byte getMu(ECCurve.F2m curve) + { + BigInteger a = curve.getA().toBigInteger(); + byte mu; + + if (a.equals(ECConstants.ZERO)) + { + mu = -1; + } + else if (a.equals(ECConstants.ONE)) + { + mu = 1; + } + else + { + throw new IllegalArgumentException("No Koblitz curve (ABC), " + + "TNAF multiplication not possible"); + } + return mu; + } + + /** + * Calculates the Lucas Sequence elements Uk-1 and + * Uk or Vk-1 and + * Vk. + * @param mu The parameter μ of the elliptic curve. + * @param k The index of the second element of the Lucas Sequence to be + * returned. + * @param doV If set to true, computes Vk-1 and + * Vk, otherwise Uk-1 and + * Uk. + * @return An array with 2 elements, containing Uk-1 + * and Uk or Vk-1 + * and Vk. + */ + public static BigInteger[] getLucas(byte mu, int k, boolean doV) + { + if (!((mu == 1) || (mu == -1))) + { + throw new IllegalArgumentException("mu must be 1 or -1"); + } + + BigInteger u0; + BigInteger u1; + BigInteger u2; + + if (doV) + { + u0 = ECConstants.TWO; + u1 = BigInteger.valueOf(mu); + } + else + { + u0 = ECConstants.ZERO; + u1 = ECConstants.ONE; + } + + for (int i = 1; i < k; i++) + { + // u2 = mu*u1 - 2*u0; + BigInteger s = null; + if (mu == 1) + { + s = u1; + } + else + { + // mu == -1 + s = u1.negate(); + } + + u2 = s.subtract(u0.shiftLeft(1)); + u0 = u1; + u1 = u2; +// System.out.println(i + ": " + u2); +// System.out.println(); + } + + BigInteger[] retVal = {u0, u1}; + return retVal; + } + + /** + * Computes the auxiliary value tw. If the width is + * 4, then for mu = 1, tw = 6 and for + * mu = -1, tw = 10 + * @param mu The parameter μ of the elliptic curve. + * @param w The window width of the WTNAF. + * @return the auxiliary value tw + */ + public static BigInteger getTw(byte mu, int w) + { + if (w == 4) + { + if (mu == 1) + { + return BigInteger.valueOf(6); + } + else + { + // mu == -1 + return BigInteger.valueOf(10); + } + } + else + { + // For w <> 4, the values must be computed + BigInteger[] us = getLucas(mu, w, false); + BigInteger twoToW = ECConstants.ZERO.setBit(w); + BigInteger u1invert = us[1].modInverse(twoToW); + BigInteger tw; + tw = ECConstants.TWO.multiply(us[0]).multiply(u1invert).mod(twoToW); +// System.out.println("mu = " + mu); +// System.out.println("tw = " + tw); + return tw; + } + } + + /** + * Computes the auxiliary values s0 and + * s1 used for partial modular reduction. + * @param curve The elliptic curve for which to compute + * s0 and s1. + * @throws IllegalArgumentException if curve is not a + * Koblitz curve (Anomalous Binary Curve, ABC). + */ + public static BigInteger[] getSi(ECCurve.F2m curve) + { + if (!curve.isKoblitz()) + { + throw new IllegalArgumentException("si is defined for Koblitz curves only"); + } + + int m = curve.getM(); + int a = curve.getA().toBigInteger().intValue(); + byte mu = curve.getMu(); + int h = curve.getH().intValue(); + int index = m + 3 - a; + BigInteger[] ui = getLucas(mu, index, false); + + BigInteger dividend0; + BigInteger dividend1; + if (mu == 1) + { + dividend0 = ECConstants.ONE.subtract(ui[1]); + dividend1 = ECConstants.ONE.subtract(ui[0]); + } + else if (mu == -1) + { + dividend0 = ECConstants.ONE.add(ui[1]); + dividend1 = ECConstants.ONE.add(ui[0]); + } + else + { + throw new IllegalArgumentException("mu must be 1 or -1"); + } + + BigInteger[] si = new BigInteger[2]; + + if (h == 2) + { + si[0] = dividend0.shiftRight(1); + si[1] = dividend1.shiftRight(1).negate(); + } + else if (h == 4) + { + si[0] = dividend0.shiftRight(2); + si[1] = dividend1.shiftRight(2).negate(); + } + else + { + throw new IllegalArgumentException("h (Cofactor) must be 2 or 4"); + } + + return si; + } + + /** + * Partial modular reduction modulo + * m - 1)/(τ - 1). + * @param k The integer to be reduced. + * @param m The bitlength of the underlying finite field. + * @param a The parameter a of the elliptic curve. + * @param s The auxiliary values s0 and + * s1. + * @param mu The parameter μ of the elliptic curve. + * @param c The precision (number of bits of accuracy) of the partial + * modular reduction. + * @return ρ := k partmod (τm - 1)/(τ - 1) + */ + public static ZTauElement partModReduction(BigInteger k, int m, byte a, + BigInteger[] s, byte mu, byte c) + { + // d0 = s[0] + mu*s[1]; mu is either 1 or -1 + BigInteger d0; + if (mu == 1) + { + d0 = s[0].add(s[1]); + } + else + { + d0 = s[0].subtract(s[1]); + } + + BigInteger[] v = getLucas(mu, m, true); + BigInteger vm = v[1]; + + SimpleBigDecimal lambda0 = approximateDivisionByN( + k, s[0], vm, a, m, c); + + SimpleBigDecimal lambda1 = approximateDivisionByN( + k, s[1], vm, a, m, c); + + ZTauElement q = round(lambda0, lambda1, mu); + + // r0 = n - d0*q0 - 2*s1*q1 + BigInteger r0 = k.subtract(d0.multiply(q.u)).subtract( + BigInteger.valueOf(2).multiply(s[1]).multiply(q.v)); + + // r1 = s1*q0 - s0*q1 + BigInteger r1 = s[1].multiply(q.u).subtract(s[0].multiply(q.v)); + + return new ZTauElement(r0, r1); + } + + /** + * Multiplies a {@link com.google.bitcoin.bouncycastle.math.ec.ECPoint.F2m ECPoint.F2m} + * by a BigInteger using the reduced τ-adic + * NAF (RTNAF) method. + * @param p The ECPoint.F2m to multiply. + * @param k The BigInteger by which to multiply p. + * @return k * p + */ + public static ECPoint.F2m multiplyRTnaf(ECPoint.F2m p, BigInteger k) + { + ECCurve.F2m curve = (ECCurve.F2m) p.getCurve(); + int m = curve.getM(); + byte a = (byte) curve.getA().toBigInteger().intValue(); + byte mu = curve.getMu(); + BigInteger[] s = curve.getSi(); + ZTauElement rho = partModReduction(k, m, a, s, mu, (byte)10); + + return multiplyTnaf(p, rho); + } + + /** + * Multiplies a {@link com.google.bitcoin.bouncycastle.math.ec.ECPoint.F2m ECPoint.F2m} + * by an element λ of Z[τ] + * using the τ-adic NAF (TNAF) method. + * @param p The ECPoint.F2m to multiply. + * @param lambda The element λ of + * Z[τ]. + * @return λ * p + */ + public static ECPoint.F2m multiplyTnaf(ECPoint.F2m p, ZTauElement lambda) + { + ECCurve.F2m curve = (ECCurve.F2m)p.getCurve(); + byte mu = curve.getMu(); + byte[] u = tauAdicNaf(mu, lambda); + + ECPoint.F2m q = multiplyFromTnaf(p, u); + + return q; + } + + /** + * Multiplies a {@link com.google.bitcoin.bouncycastle.math.ec.ECPoint.F2m ECPoint.F2m} + * by an element λ of Z[τ] + * using the τ-adic NAF (TNAF) method, given the TNAF + * of λ. + * @param p The ECPoint.F2m to multiply. + * @param u The the TNAF of λ.. + * @return λ * p + */ + public static ECPoint.F2m multiplyFromTnaf(ECPoint.F2m p, byte[] u) + { + ECCurve.F2m curve = (ECCurve.F2m)p.getCurve(); + ECPoint.F2m q = (ECPoint.F2m) curve.getInfinity(); + for (int i = u.length - 1; i >= 0; i--) + { + q = tau(q); + if (u[i] == 1) + { + q = (ECPoint.F2m)q.addSimple(p); + } + else if (u[i] == -1) + { + q = (ECPoint.F2m)q.subtractSimple(p); + } + } + return q; + } + + /** + * Computes the [τ]-adic window NAF of an element + * λ of Z[τ]. + * @param mu The parameter μ of the elliptic curve. + * @param lambda The element λ of + * Z[τ] of which to compute the + * [τ]-adic NAF. + * @param width The window width of the resulting WNAF. + * @param pow2w 2width. + * @param tw The auxiliary value tw. + * @param alpha The αu's for the window width. + * @return The [τ]-adic window NAF of + * λ. + */ + public static byte[] tauAdicWNaf(byte mu, ZTauElement lambda, + byte width, BigInteger pow2w, BigInteger tw, ZTauElement[] alpha) + { + if (!((mu == 1) || (mu == -1))) + { + throw new IllegalArgumentException("mu must be 1 or -1"); + } + + BigInteger norm = norm(mu, lambda); + + // Ceiling of log2 of the norm + int log2Norm = norm.bitLength(); + + // If length(TNAF) > 30, then length(TNAF) < log2Norm + 3.52 + int maxLength = log2Norm > 30 ? log2Norm + 4 + width : 34 + width; + + // The array holding the TNAF + byte[] u = new byte[maxLength]; + + // 2^(width - 1) + BigInteger pow2wMin1 = pow2w.shiftRight(1); + + // Split lambda into two BigIntegers to simplify calculations + BigInteger r0 = lambda.u; + BigInteger r1 = lambda.v; + int i = 0; + + // while lambda <> (0, 0) + while (!((r0.equals(ECConstants.ZERO))&&(r1.equals(ECConstants.ZERO)))) + { + // if r0 is odd + if (r0.testBit(0)) + { + // uUnMod = r0 + r1*tw mod 2^width + BigInteger uUnMod + = r0.add(r1.multiply(tw)).mod(pow2w); + + byte uLocal; + // if uUnMod >= 2^(width - 1) + if (uUnMod.compareTo(pow2wMin1) >= 0) + { + uLocal = (byte) uUnMod.subtract(pow2w).intValue(); + } + else + { + uLocal = (byte) uUnMod.intValue(); + } + // uLocal is now in [-2^(width-1), 2^(width-1)-1] + + u[i] = uLocal; + boolean s = true; + if (uLocal < 0) + { + s = false; + uLocal = (byte)-uLocal; + } + // uLocal is now >= 0 + + if (s) + { + r0 = r0.subtract(alpha[uLocal].u); + r1 = r1.subtract(alpha[uLocal].v); + } + else + { + r0 = r0.add(alpha[uLocal].u); + r1 = r1.add(alpha[uLocal].v); + } + } + else + { + u[i] = 0; + } + + BigInteger t = r0; + + if (mu == 1) + { + r0 = r1.add(r0.shiftRight(1)); + } + else + { + // mu == -1 + r0 = r1.subtract(r0.shiftRight(1)); + } + r1 = t.shiftRight(1).negate(); + i++; + } + return u; + } + + /** + * Does the precomputation for WTNAF multiplication. + * @param p The ECPoint for which to do the precomputation. + * @param a The parameter a of the elliptic curve. + * @return The precomputation array for p. + */ + public static ECPoint.F2m[] getPreComp(ECPoint.F2m p, byte a) + { + ECPoint.F2m[] pu; + pu = new ECPoint.F2m[16]; + pu[1] = p; + byte[][] alphaTnaf; + if (a == 0) + { + alphaTnaf = Tnaf.alpha0Tnaf; + } + else + { + // a == 1 + alphaTnaf = Tnaf.alpha1Tnaf; + } + + int precompLen = alphaTnaf.length; + for (int i = 3; i < precompLen; i = i + 2) + { + pu[i] = Tnaf.multiplyFromTnaf(p, alphaTnaf[i]); + } + + return pu; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/WNafMultiplier.java b/src/com/google/bitcoin/bouncycastle/math/ec/WNafMultiplier.java new file mode 100644 index 000000000..7bd79ae96 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/WNafMultiplier.java @@ -0,0 +1,240 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +import java.math.BigInteger; + +/** + * Class implementing the WNAF (Window Non-Adjacent Form) multiplication + * algorithm. + */ +class WNafMultiplier implements ECMultiplier +{ + /** + * Computes the Window NAF (non-adjacent Form) of an integer. + * @param width The width w of the Window NAF. The width is + * defined as the minimal number w, such that for any + * w consecutive digits in the resulting representation, at + * most one is non-zero. + * @param k The integer of which the Window NAF is computed. + * @return The Window NAF of the given width, such that the following holds: + * k = ∑i=0l-1 ki2i + * , where the ki denote the elements of the + * returned byte[]. + */ + public byte[] windowNaf(byte width, BigInteger k) + { + // The window NAF is at most 1 element longer than the binary + // representation of the integer k. byte can be used instead of short or + // int unless the window width is larger than 8. For larger width use + // short or int. However, a width of more than 8 is not efficient for + // m = log2(q) smaller than 2305 Bits. Note: Values for m larger than + // 1000 Bits are currently not used in practice. + byte[] wnaf = new byte[k.bitLength() + 1]; + + // 2^width as short and BigInteger + short pow2wB = (short)(1 << width); + BigInteger pow2wBI = BigInteger.valueOf(pow2wB); + + int i = 0; + + // The actual length of the WNAF + int length = 0; + + // while k >= 1 + while (k.signum() > 0) + { + // if k is odd + if (k.testBit(0)) + { + // k mod 2^width + BigInteger remainder = k.mod(pow2wBI); + + // if remainder > 2^(width - 1) - 1 + if (remainder.testBit(width - 1)) + { + wnaf[i] = (byte)(remainder.intValue() - pow2wB); + } + else + { + wnaf[i] = (byte)remainder.intValue(); + } + // wnaf[i] is now in [-2^(width-1), 2^(width-1)-1] + + k = k.subtract(BigInteger.valueOf(wnaf[i])); + length = i; + } + else + { + wnaf[i] = 0; + } + + // k = k/2 + k = k.shiftRight(1); + i++; + } + + length++; + + // Reduce the WNAF array to its actual length + byte[] wnafShort = new byte[length]; + System.arraycopy(wnaf, 0, wnafShort, 0, length); + return wnafShort; + } + + /** + * Multiplies this by an integer k using the + * Window NAF method. + * @param k The integer by which this is multiplied. + * @return A new ECPoint which equals this + * multiplied by k. + */ + public ECPoint multiply(ECPoint p, BigInteger k, PreCompInfo preCompInfo) + { + WNafPreCompInfo wnafPreCompInfo; + + if ((preCompInfo != null) && (preCompInfo instanceof WNafPreCompInfo)) + { + wnafPreCompInfo = (WNafPreCompInfo)preCompInfo; + } + else + { + // Ignore empty PreCompInfo or PreCompInfo of incorrect type + wnafPreCompInfo = new WNafPreCompInfo(); + } + + // floor(log2(k)) + int m = k.bitLength(); + + // width of the Window NAF + byte width; + + // Required length of precomputation array + int reqPreCompLen; + + // Determine optimal width and corresponding length of precomputation + // array based on literature values + if (m < 13) + { + width = 2; + reqPreCompLen = 1; + } + else + { + if (m < 41) + { + width = 3; + reqPreCompLen = 2; + } + else + { + if (m < 121) + { + width = 4; + reqPreCompLen = 4; + } + else + { + if (m < 337) + { + width = 5; + reqPreCompLen = 8; + } + else + { + if (m < 897) + { + width = 6; + reqPreCompLen = 16; + } + else + { + if (m < 2305) + { + width = 7; + reqPreCompLen = 32; + } + else + { + width = 8; + reqPreCompLen = 127; + } + } + } + } + } + } + + // The length of the precomputation array + int preCompLen = 1; + + ECPoint[] preComp = wnafPreCompInfo.getPreComp(); + ECPoint twiceP = wnafPreCompInfo.getTwiceP(); + + // Check if the precomputed ECPoints already exist + if (preComp == null) + { + // Precomputation must be performed from scratch, create an empty + // precomputation array of desired length + preComp = new ECPoint[]{ p }; + } + else + { + // Take the already precomputed ECPoints to start with + preCompLen = preComp.length; + } + + if (twiceP == null) + { + // Compute twice(p) + twiceP = p.twice(); + } + + if (preCompLen < reqPreCompLen) + { + // Precomputation array must be made bigger, copy existing preComp + // array into the larger new preComp array + ECPoint[] oldPreComp = preComp; + preComp = new ECPoint[reqPreCompLen]; + System.arraycopy(oldPreComp, 0, preComp, 0, preCompLen); + + for (int i = preCompLen; i < reqPreCompLen; i++) + { + // Compute the new ECPoints for the precomputation array. + // The values 1, 3, 5, ..., 2^(width-1)-1 times p are + // computed + preComp[i] = twiceP.add(preComp[i - 1]); + } + } + + // Compute the Window NAF of the desired width + byte[] wnaf = windowNaf(width, k); + int l = wnaf.length; + + // Apply the Window NAF to p using the precomputed ECPoint values. + ECPoint q = p.getCurve().getInfinity(); + for (int i = l - 1; i >= 0; i--) + { + q = q.twice(); + + if (wnaf[i] != 0) + { + if (wnaf[i] > 0) + { + q = q.add(preComp[(wnaf[i] - 1)/2]); + } + else + { + // wnaf[i] < 0 + q = q.subtract(preComp[(-wnaf[i] - 1)/2]); + } + } + } + + // Set PreCompInfo in ECPoint, such that it is available for next + // multiplication. + wnafPreCompInfo.setPreComp(preComp); + wnafPreCompInfo.setTwiceP(twiceP); + p.setPreCompInfo(wnafPreCompInfo); + return q; + } + +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/WNafPreCompInfo.java b/src/com/google/bitcoin/bouncycastle/math/ec/WNafPreCompInfo.java new file mode 100644 index 000000000..c3397980a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/WNafPreCompInfo.java @@ -0,0 +1,44 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +/** + * Class holding precomputation data for the WNAF (Window Non-Adjacent Form) + * algorithm. + */ +class WNafPreCompInfo implements PreCompInfo +{ + /** + * Array holding the precomputed ECPoints used for the Window + * NAF multiplication in + * {@link org.bouncycastle.math.ec.multiplier.WNafMultiplier.multiply() + * WNafMultiplier.multiply()}. + */ + private ECPoint[] preComp = null; + + /** + * Holds an ECPoint representing twice(this). Used for the + * Window NAF multiplication in + * {@link org.bouncycastle.math.ec.multiplier.WNafMultiplier.multiply() + * WNafMultiplier.multiply()}. + */ + private ECPoint twiceP = null; + + protected ECPoint[] getPreComp() + { + return preComp; + } + + protected void setPreComp(ECPoint[] preComp) + { + this.preComp = preComp; + } + + protected ECPoint getTwiceP() + { + return twiceP; + } + + protected void setTwiceP(ECPoint twiceThis) + { + this.twiceP = twiceThis; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/WTauNafMultiplier.java b/src/com/google/bitcoin/bouncycastle/math/ec/WTauNafMultiplier.java new file mode 100644 index 000000000..68105f9e9 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/WTauNafMultiplier.java @@ -0,0 +1,119 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +import java.math.BigInteger; + +/** + * Class implementing the WTNAF (Window + * τ-adic Non-Adjacent Form) algorithm. + */ +class WTauNafMultiplier implements ECMultiplier +{ + /** + * Multiplies a {@link com.google.bitcoin.bouncycastle.math.ec.ECPoint.F2m ECPoint.F2m} + * by k using the reduced τ-adic NAF (RTNAF) + * method. + * @param p The ECPoint.F2m to multiply. + * @param k The integer by which to multiply k. + * @return p multiplied by k. + */ + public ECPoint multiply(ECPoint point, BigInteger k, PreCompInfo preCompInfo) + { + if (!(point instanceof ECPoint.F2m)) + { + throw new IllegalArgumentException("Only ECPoint.F2m can be " + + "used in WTauNafMultiplier"); + } + + ECPoint.F2m p = (ECPoint.F2m)point; + + ECCurve.F2m curve = (ECCurve.F2m) p.getCurve(); + int m = curve.getM(); + byte a = curve.getA().toBigInteger().byteValue(); + byte mu = curve.getMu(); + BigInteger[] s = curve.getSi(); + + ZTauElement rho = Tnaf.partModReduction(k, m, a, s, mu, (byte)10); + + return multiplyWTnaf(p, rho, preCompInfo, a, mu); + } + + /** + * Multiplies a {@link com.google.bitcoin.bouncycastle.math.ec.ECPoint.F2m ECPoint.F2m} + * by an element λ of Z[τ] using + * the τ-adic NAF (TNAF) method. + * @param p The ECPoint.F2m to multiply. + * @param lambda The element λ of + * Z[τ] of which to compute the + * [τ]-adic NAF. + * @return p multiplied by λ. + */ + private ECPoint.F2m multiplyWTnaf(ECPoint.F2m p, ZTauElement lambda, + PreCompInfo preCompInfo, byte a, byte mu) + { + ZTauElement[] alpha; + if (a == 0) + { + alpha = Tnaf.alpha0; + } + else + { + // a == 1 + alpha = Tnaf.alpha1; + } + + BigInteger tw = Tnaf.getTw(mu, Tnaf.WIDTH); + + byte[]u = Tnaf.tauAdicWNaf(mu, lambda, Tnaf.WIDTH, + BigInteger.valueOf(Tnaf.POW_2_WIDTH), tw, alpha); + + return multiplyFromWTnaf(p, u, preCompInfo); + } + + /** + * Multiplies a {@link com.google.bitcoin.bouncycastle.math.ec.ECPoint.F2m ECPoint.F2m} + * by an element λ of Z[τ] + * using the window τ-adic NAF (TNAF) method, given the + * WTNAF of λ. + * @param p The ECPoint.F2m to multiply. + * @param u The the WTNAF of λ.. + * @return λ * p + */ + private static ECPoint.F2m multiplyFromWTnaf(ECPoint.F2m p, byte[] u, + PreCompInfo preCompInfo) + { + ECCurve.F2m curve = (ECCurve.F2m)p.getCurve(); + byte a = curve.getA().toBigInteger().byteValue(); + + ECPoint.F2m[] pu; + if ((preCompInfo == null) || !(preCompInfo instanceof WTauNafPreCompInfo)) + { + pu = Tnaf.getPreComp(p, a); + p.setPreCompInfo(new WTauNafPreCompInfo(pu)); + } + else + { + pu = ((WTauNafPreCompInfo)preCompInfo).getPreComp(); + } + + // q = infinity + ECPoint.F2m q = (ECPoint.F2m) p.getCurve().getInfinity(); + for (int i = u.length - 1; i >= 0; i--) + { + q = Tnaf.tau(q); + if (u[i] != 0) + { + if (u[i] > 0) + { + q = q.addSimple(pu[u[i]]); + } + else + { + // u[i] < 0 + q = q.subtractSimple(pu[-u[i]]); + } + } + } + + return q; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/WTauNafPreCompInfo.java b/src/com/google/bitcoin/bouncycastle/math/ec/WTauNafPreCompInfo.java new file mode 100644 index 000000000..5ac768250 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/WTauNafPreCompInfo.java @@ -0,0 +1,39 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +/** + * Class holding precomputation data for the WTNAF (Window + * τ-adic Non-Adjacent Form) algorithm. + */ +class WTauNafPreCompInfo implements PreCompInfo +{ + /** + * Array holding the precomputed ECPoint.F2ms used for the + * WTNAF multiplication in + * {@link org.bouncycastle.math.ec.multiplier.WTauNafMultiplier.multiply() + * WTauNafMultiplier.multiply()}. + */ + private ECPoint.F2m[] preComp = null; + + /** + * Constructor for WTauNafPreCompInfo + * @param preComp Array holding the precomputed ECPoint.F2ms + * used for the WTNAF multiplication in + * {@link org.bouncycastle.math.ec.multiplier.WTauNafMultiplier.multiply() + * WTauNafMultiplier.multiply()}. + */ + WTauNafPreCompInfo(ECPoint.F2m[] preComp) + { + this.preComp = preComp; + } + + /** + * @return the array holding the precomputed ECPoint.F2ms + * used for the WTNAF multiplication in + * {@link org.bouncycastle.math.ec.multiplier.WTauNafMultiplier.multiply() + * WTauNafMultiplier.multiply()}. + */ + protected ECPoint.F2m[] getPreComp() + { + return preComp; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/math/ec/ZTauElement.java b/src/com/google/bitcoin/bouncycastle/math/ec/ZTauElement.java new file mode 100644 index 000000000..5ebe52ae6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/math/ec/ZTauElement.java @@ -0,0 +1,37 @@ +package com.google.bitcoin.bouncycastle.math.ec; + +import java.math.BigInteger; + +/** + * Class representing an element of Z[τ]. Let + * λ be an element of Z[τ]. Then + * λ is given as λ = u + vτ. The + * components u and v may be used directly, there + * are no accessor methods. + * Immutable class. + */ +class ZTauElement +{ + /** + * The "real" part of λ. + */ + public final BigInteger u; + + /** + * The "τ-adic" part of λ. + */ + public final BigInteger v; + + /** + * Constructor for an element λ of + * Z[τ]. + * @param u The "real" part of λ. + * @param v The "τ-adic" part of + * λ. + */ + public ZTauElement(BigInteger u, BigInteger v) + { + this.u = u; + this.v = v; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/Arrays.java b/src/com/google/bitcoin/bouncycastle/util/Arrays.java new file mode 100644 index 000000000..7a9d71b55 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/Arrays.java @@ -0,0 +1,214 @@ +package com.google.bitcoin.bouncycastle.util; + +/** + * General array utilities. + */ +public final class Arrays +{ + private Arrays() + { + // static class, hide constructor + } + + public static boolean areEqual( + boolean[] a, + boolean[] b) + { + if (a == b) + { + return true; + } + + if (a == null || b == null) + { + return false; + } + + if (a.length != b.length) + { + return false; + } + + for (int i = 0; i != a.length; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + + return true; + } + + public static boolean areEqual( + byte[] a, + byte[] b) + { + if (a == b) + { + return true; + } + + if (a == null || b == null) + { + return false; + } + + if (a.length != b.length) + { + return false; + } + + for (int i = 0; i != a.length; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + + return true; + } + + /** + * A constant time equals comparison - does not terminate early if + * test will fail. + * + * @param a first array + * @param b second array + * @return true if arrays equal, false otherwise. + */ + public static boolean constantTimeAreEqual( + byte[] a, + byte[] b) + { + if (a == b) + { + return true; + } + + if (a == null || b == null) + { + return false; + } + + if (a.length != b.length) + { + return false; + } + + int nonEqual = 0; + + for (int i = 0; i != a.length; i++) + { + nonEqual |= (a[i] ^ b[i]); + } + + return nonEqual == 0; + } + + public static boolean areEqual( + int[] a, + int[] b) + { + if (a == b) + { + return true; + } + + if (a == null || b == null) + { + return false; + } + + if (a.length != b.length) + { + return false; + } + + for (int i = 0; i != a.length; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + + return true; + } + + public static void fill( + byte[] array, + byte value) + { + for (int i = 0; i < array.length; i++) + { + array[i] = value; + } + } + + public static void fill( + long[] array, + long value) + { + for (int i = 0; i < array.length; i++) + { + array[i] = value; + } + } + + public static void fill( + short[] array, + short value) + { + for (int i = 0; i < array.length; i++) + { + array[i] = value; + } + } + + public static int hashCode(byte[] data) + { + if (data == null) + { + return 0; + } + + int i = data.length; + int hc = i + 1; + + while (--i >= 0) + { + hc *= 257; + hc ^= data[i]; + } + + return hc; + } + + public static byte[] clone(byte[] data) + { + if (data == null) + { + return null; + } + byte[] copy = new byte[data.length]; + + System.arraycopy(data, 0, copy, 0, data.length); + + return copy; + } + + public static int[] clone(int[] data) + { + if (data == null) + { + return null; + } + int[] copy = new int[data.length]; + + System.arraycopy(data, 0, copy, 0, data.length); + + return copy; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/BigIntegers.java b/src/com/google/bitcoin/bouncycastle/util/BigIntegers.java new file mode 100644 index 000000000..bbcd0c25a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/BigIntegers.java @@ -0,0 +1,78 @@ +package com.google.bitcoin.bouncycastle.util; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * BigInteger utilities. + */ +public final class BigIntegers +{ + private static final int MAX_ITERATIONS = 1000; + private static final BigInteger ZERO = BigInteger.valueOf(0); + + /** + * Return the passed in value as an unsigned byte array. + * + * @param value value to be converted. + * @return a byte array without a leading zero byte if present in the signed encoding. + */ + public static byte[] asUnsignedByteArray( + BigInteger value) + { + byte[] bytes = value.toByteArray(); + + if (bytes[0] == 0) + { + byte[] tmp = new byte[bytes.length - 1]; + + System.arraycopy(bytes, 1, tmp, 0, tmp.length); + + return tmp; + } + + return bytes; + } + + /** + * Return a random BigInteger not less than 'min' and not greater than 'max' + * + * @param min the least value that may be generated + * @param max the greatest value that may be generated + * @param random the source of randomness + * @return a random BigInteger value in the range [min,max] + */ + public static BigInteger createRandomInRange( + BigInteger min, + BigInteger max, + SecureRandom random) + { + int cmp = min.compareTo(max); + if (cmp >= 0) + { + if (cmp > 0) + { + throw new IllegalArgumentException("'min' may not be greater than 'max'"); + } + + return min; + } + + if (min.bitLength() > max.bitLength() / 2) + { + return createRandomInRange(ZERO, max.subtract(min), random).add(min); + } + + for (int i = 0; i < MAX_ITERATIONS; ++i) + { + BigInteger x = new BigInteger(max.bitLength(), random); + if (x.compareTo(min) >= 0 && x.compareTo(max) <= 0) + { + return x; + } + } + + // fall back to a faster (restricted) method + return new BigInteger(max.subtract(min).bitLength() - 1, random).add(min); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/CollectionStore.java b/src/com/google/bitcoin/bouncycastle/util/CollectionStore.java new file mode 100644 index 000000000..75e0f1091 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/CollectionStore.java @@ -0,0 +1,57 @@ +package com.google.bitcoin.bouncycastle.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * A simple collection backed store. + */ +public class CollectionStore + implements Store +{ + private Collection _local; + + /** + * Basic constructor. + * + * @param collection - initial contents for the store, this is copied. + */ + public CollectionStore( + Collection collection) + { + _local = new ArrayList(collection); + } + + /** + * Return the matches in the collection for the passed in selector. + * + * @param selector the selector to match against. + * @return a possibly empty collection of matching objects. + */ + public Collection getMatches(Selector selector) + { + if (selector == null) + { + return new ArrayList(_local); + } + else + { + List col = new ArrayList(); + Iterator iter = _local.iterator(); + + while (iter.hasNext()) + { + Object obj = iter.next(); + + if (selector.match(obj)) + { + col.add(obj); + } + } + + return col; + } + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/IPAddress.java b/src/com/google/bitcoin/bouncycastle/util/IPAddress.java new file mode 100644 index 000000000..51903c7fa --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/IPAddress.java @@ -0,0 +1,188 @@ +package com.google.bitcoin.bouncycastle.util; + +public class IPAddress +{ + /** + * Validate the given IPv4 or IPv6 address. + * + * @param address the IP address as a String. + * + * @return true if a valid address, false otherwise + */ + public static boolean isValid( + String address) + { + return isValidIPv4(address) || isValidIPv6(address); + } + + /** + * Validate the given IPv4 or IPv6 address and netmask. + * + * @param address the IP address as a String. + * + * @return true if a valid address with netmask, false otherwise + */ + public static boolean isValidWithNetMask( + String address) + { + return isValidIPv4WithNetmask(address) || isValidIPv6WithNetmask(address); + } + + /** + * Validate the given IPv4 address. + * + * @param address the IP address as a String. + * + * @return true if a valid IPv4 address, false otherwise + */ + public static boolean isValidIPv4( + String address) + { + if (address.length() == 0) + { + return false; + } + + int octet; + int octets = 0; + + String temp = address+"."; + + int pos; + int start = 0; + while (start < temp.length() + && (pos = temp.indexOf('.', start)) > start) + { + if (octets == 4) + { + return false; + } + try + { + octet = Integer.parseInt(temp.substring(start, pos)); + } + catch (NumberFormatException ex) + { + return false; + } + if (octet < 0 || octet > 255) + { + return false; + } + start = pos + 1; + octets++; + } + + return octets == 4; + } + + public static boolean isValidIPv4WithNetmask( + String address) + { + int index = address.indexOf("/"); + String mask = address.substring(index + 1); + + return (index > 0) && isValidIPv4(address.substring(0, index)) + && (isValidIPv4(mask) || isMaskValue(mask, 32)); + } + + public static boolean isValidIPv6WithNetmask( + String address) + { + int index = address.indexOf("/"); + String mask = address.substring(index + 1); + + return (index > 0) && (isValidIPv6(address.substring(0, index)) + && (isValidIPv6(mask) || isMaskValue(mask, 128))); + } + + private static boolean isMaskValue(String component, int size) + { + try + { + int value = Integer.parseInt(component); + + return value >= 0 && value <= size; + } + catch (NumberFormatException e) + { + return false; + } + } + + /** + * Validate the given IPv6 address. + * + * @param address the IP address as a String. + * + * @return true if a valid IPv4 address, false otherwise + */ + public static boolean isValidIPv6( + String address) + { + if (address.length() == 0) + { + return false; + } + + int octet; + int octets = 0; + + String temp = address + ":"; + boolean doubleColonFound = false; + int pos; + int start = 0; + while (start < temp.length() + && (pos = temp.indexOf(':', start)) >= start) + { + if (octets == 8) + { + return false; + } + + if (start != pos) + { + String value = temp.substring(start, pos); + + if (pos == (temp.length() - 1) && value.indexOf('.') > 0) + { + if (!isValidIPv4(value)) + { + return false; + } + + octets++; // add an extra one as address covers 2 words. + } + else + { + try + { + octet = Integer.parseInt(temp.substring(start, pos), 16); + } + catch (NumberFormatException ex) + { + return false; + } + if (octet < 0 || octet > 0xffff) + { + return false; + } + } + } + else + { + if (pos != 1 && pos != temp.length() - 1 && doubleColonFound) + { + return false; + } + doubleColonFound = true; + } + start = pos + 1; + octets++; + } + + return octets == 8 || doubleColonFound; + } +} + + diff --git a/src/com/google/bitcoin/bouncycastle/util/Selector.java b/src/com/google/bitcoin/bouncycastle/util/Selector.java new file mode 100644 index 000000000..30295d0ec --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/Selector.java @@ -0,0 +1,9 @@ +package com.google.bitcoin.bouncycastle.util; + +public interface Selector + extends Cloneable +{ + boolean match(Object obj); + + Object clone(); +} diff --git a/src/com/google/bitcoin/bouncycastle/util/Store.java b/src/com/google/bitcoin/bouncycastle/util/Store.java new file mode 100644 index 000000000..13e1f55b6 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/Store.java @@ -0,0 +1,9 @@ +package com.google.bitcoin.bouncycastle.util; + +import java.util.Collection; + +public interface Store +{ + Collection getMatches(Selector selector) + throws StoreException; +} diff --git a/src/com/google/bitcoin/bouncycastle/util/StoreException.java b/src/com/google/bitcoin/bouncycastle/util/StoreException.java new file mode 100644 index 000000000..8c7aea76d --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/StoreException.java @@ -0,0 +1,18 @@ +package com.google.bitcoin.bouncycastle.util; + +public class StoreException + extends RuntimeException +{ + private Throwable _e; + + public StoreException(String s, Throwable e) + { + super(s); + _e = e; + } + + public Throwable getCause() + { + return _e; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/StreamParser.java b/src/com/google/bitcoin/bouncycastle/util/StreamParser.java new file mode 100644 index 000000000..4d10c16fc --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/StreamParser.java @@ -0,0 +1,10 @@ +package com.google.bitcoin.bouncycastle.util; + +import java.util.Collection; + +public interface StreamParser +{ + Object read() throws StreamParsingException; + + Collection readAll() throws StreamParsingException; +} diff --git a/src/com/google/bitcoin/bouncycastle/util/StreamParsingException.java b/src/com/google/bitcoin/bouncycastle/util/StreamParsingException.java new file mode 100644 index 000000000..e0bfb5bd5 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/StreamParsingException.java @@ -0,0 +1,18 @@ +package com.google.bitcoin.bouncycastle.util; + +public class StreamParsingException + extends Exception +{ + Throwable _e; + + public StreamParsingException(String message, Throwable e) + { + super(message); + _e = e; + } + + public Throwable getCause() + { + return _e; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/Strings.java b/src/com/google/bitcoin/bouncycastle/util/Strings.java new file mode 100644 index 000000000..284bed1c3 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/Strings.java @@ -0,0 +1,246 @@ +package com.google.bitcoin.bouncycastle.util; + +import java.io.ByteArrayOutputStream; +import java.util.Vector; + +public final class Strings +{ + public static String fromUTF8ByteArray(byte[] bytes) + { + int i = 0; + int length = 0; + + while (i < bytes.length) + { + length++; + if ((bytes[i] & 0xf0) == 0xf0) + { + // surrogate pair + length++; + i += 4; + } + else if ((bytes[i] & 0xe0) == 0xe0) + { + i += 3; + } + else if ((bytes[i] & 0xc0) == 0xc0) + { + i += 2; + } + else + { + i += 1; + } + } + + char[] cs = new char[length]; + + i = 0; + length = 0; + + while (i < bytes.length) + { + char ch; + + if ((bytes[i] & 0xf0) == 0xf0) + { + int codePoint = ((bytes[i] & 0x03) << 18) | ((bytes[i+1] & 0x3F) << 12) | ((bytes[i+2] & 0x3F) << 6) | (bytes[i+3] & 0x3F); + int U = codePoint - 0x10000; + char W1 = (char)(0xD800 | (U >> 10)); + char W2 = (char)(0xDC00 | (U & 0x3FF)); + cs[length++] = W1; + ch = W2; + i += 4; + } + else if ((bytes[i] & 0xe0) == 0xe0) + { + ch = (char)(((bytes[i] & 0x0f) << 12) + | ((bytes[i + 1] & 0x3f) << 6) | (bytes[i + 2] & 0x3f)); + i += 3; + } + else if ((bytes[i] & 0xd0) == 0xd0) + { + ch = (char)(((bytes[i] & 0x1f) << 6) | (bytes[i + 1] & 0x3f)); + i += 2; + } + else if ((bytes[i] & 0xc0) == 0xc0) + { + ch = (char)(((bytes[i] & 0x1f) << 6) | (bytes[i + 1] & 0x3f)); + i += 2; + } + else + { + ch = (char)(bytes[i] & 0xff); + i += 1; + } + + cs[length++] = ch; + } + + return new String(cs); + } + + public static byte[] toUTF8ByteArray(String string) + { + return toUTF8ByteArray(string.toCharArray()); + } + + public static byte[] toUTF8ByteArray(char[] string) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + char[] c = string; + int i = 0; + + while (i < c.length) + { + char ch = c[i]; + + if (ch < 0x0080) + { + bOut.write(ch); + } + else if (ch < 0x0800) + { + bOut.write(0xc0 | (ch >> 6)); + bOut.write(0x80 | (ch & 0x3f)); + } + // surrogate pair + else if (ch >= 0xD800 && ch <= 0xDFFF) + { + // in error - can only happen, if the Java String class has a + // bug. + if (i + 1 >= c.length) + { + throw new IllegalStateException("invalid UTF-16 codepoint"); + } + char W1 = ch; + ch = c[++i]; + char W2 = ch; + // in error - can only happen, if the Java String class has a + // bug. + if (W1 > 0xDBFF) + { + throw new IllegalStateException("invalid UTF-16 codepoint"); + } + int codePoint = (((W1 & 0x03FF) << 10) | (W2 & 0x03FF)) + 0x10000; + bOut.write(0xf0 | (codePoint >> 18)); + bOut.write(0x80 | ((codePoint >> 12) & 0x3F)); + bOut.write(0x80 | ((codePoint >> 6) & 0x3F)); + bOut.write(0x80 | (codePoint & 0x3F)); + } + else + { + bOut.write(0xe0 | (ch >> 12)); + bOut.write(0x80 | ((ch >> 6) & 0x3F)); + bOut.write(0x80 | (ch & 0x3F)); + } + + i++; + } + + return bOut.toByteArray(); + } + + /** + * A locale independent version of toUpperCase. + * + * @param string input to be converted + * @return a US Ascii uppercase version + */ + public static String toUpperCase(String string) + { + boolean changed = false; + char[] chars = string.toCharArray(); + + for (int i = 0; i != chars.length; i++) + { + char ch = chars[i]; + if ('a' <= ch && 'z' >= ch) + { + changed = true; + chars[i] = (char)(ch - 'a' + 'A'); + } + } + + if (changed) + { + return new String(chars); + } + + return string; + } + + /** + * A locale independent version of toLowerCase. + * + * @param string input to be converted + * @return a US ASCII lowercase version + */ + public static String toLowerCase(String string) + { + boolean changed = false; + char[] chars = string.toCharArray(); + + for (int i = 0; i != chars.length; i++) + { + char ch = chars[i]; + if ('A' <= ch && 'Z' >= ch) + { + changed = true; + chars[i] = (char)(ch - 'A' + 'a'); + } + } + + if (changed) + { + return new String(chars); + } + + return string; + } + + public static byte[] toByteArray(String string) + { + byte[] bytes = new byte[string.length()]; + + for (int i = 0; i != bytes.length; i++) + { + char ch = string.charAt(i); + + bytes[i] = (byte)ch; + } + + return bytes; + } + + public static String[] split(String input, char delimiter) + { + Vector v = new Vector(); + boolean moreTokens = true; + String subString; + + while (moreTokens) + { + int tokenLocation = input.indexOf(delimiter); + if (tokenLocation > 0) + { + subString = input.substring(0, tokenLocation); + v.addElement(subString); + input = input.substring(tokenLocation + 1); + } + else + { + moreTokens = false; + v.addElement(input); + } + } + + String[] res = new String[v.size()]; + + for (int i = 0; i != res.length; i++) + { + res[i] = (String)v.elementAt(i); + } + return res; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/encoders/Base64.java b/src/com/google/bitcoin/bouncycastle/util/encoders/Base64.java new file mode 100644 index 000000000..fdbbd5e9a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/encoders/Base64.java @@ -0,0 +1,121 @@ +package com.google.bitcoin.bouncycastle.util.encoders; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class Base64 +{ + private static final Encoder encoder = new Base64Encoder(); + + /** + * encode the input data producing a base 64 encoded byte array. + * + * @return a byte array containing the base 64 encoded data. + */ + public static byte[] encode( + byte[] data) + { + int len = (data.length + 2) / 3 * 4; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); + + try + { + encoder.encode(data, 0, data.length, bOut); + } + catch (IOException e) + { + throw new RuntimeException("exception encoding base64 string: " + e); + } + + return bOut.toByteArray(); + } + + /** + * Encode the byte data to base 64 writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int encode( + byte[] data, + OutputStream out) + throws IOException + { + return encoder.encode(data, 0, data.length, out); + } + + /** + * Encode the byte data to base 64 writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int encode( + byte[] data, + int off, + int length, + OutputStream out) + throws IOException + { + return encoder.encode(data, off, length, out); + } + + /** + * decode the base 64 encoded input data. It is assumed the input data is valid. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + byte[] data) + { + int len = data.length / 4 * 3; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); + + try + { + encoder.decode(data, 0, data.length, bOut); + } + catch (IOException e) + { + throw new RuntimeException("exception decoding base64 string: " + e); + } + + return bOut.toByteArray(); + } + + /** + * decode the base 64 encoded String data - whitespace will be ignored. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + String data) + { + int len = data.length() / 4 * 3; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); + + try + { + encoder.decode(data, bOut); + } + catch (IOException e) + { + throw new RuntimeException("exception decoding base64 string: " + e); + } + + return bOut.toByteArray(); + } + + /** + * decode the base 64 encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public static int decode( + String data, + OutputStream out) + throws IOException + { + return encoder.decode(data, out); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/encoders/Base64Encoder.java b/src/com/google/bitcoin/bouncycastle/util/encoders/Base64Encoder.java new file mode 100644 index 000000000..0b5d3104a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/encoders/Base64Encoder.java @@ -0,0 +1,298 @@ +package com.google.bitcoin.bouncycastle.util.encoders; + +import java.io.IOException; +import java.io.OutputStream; + +public class Base64Encoder + implements Encoder +{ + protected 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)'/' + }; + + protected byte padding = (byte)'='; + + /* + * set up the decoding table. + */ + protected final byte[] decodingTable = new byte[128]; + + protected void initialiseDecodingTable() + { + for (int i = 0; i < encodingTable.length; i++) + { + decodingTable[encodingTable[i]] = (byte)i; + } + } + + public Base64Encoder() + { + initialiseDecodingTable(); + } + + /** + * encode the input data producing a base 64 output stream. + * + * @return the number of bytes produced. + */ + public int encode( + byte[] data, + int off, + int length, + OutputStream out) + throws IOException + { + int modulus = length % 3; + int dataLength = (length - modulus); + int a1, a2, a3; + + for (int i = off; i < off + dataLength; i += 3) + { + a1 = data[i] & 0xff; + a2 = data[i + 1] & 0xff; + a3 = data[i + 2] & 0xff; + + out.write(encodingTable[(a1 >>> 2) & 0x3f]); + out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]); + out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]); + out.write(encodingTable[a3 & 0x3f]); + } + + /* + * process the tail end. + */ + int b1, b2, b3; + int d1, d2; + + switch (modulus) + { + case 0: /* nothing left to do */ + break; + case 1: + d1 = data[off + dataLength] & 0xff; + b1 = (d1 >>> 2) & 0x3f; + b2 = (d1 << 4) & 0x3f; + + out.write(encodingTable[b1]); + out.write(encodingTable[b2]); + out.write(padding); + out.write(padding); + break; + case 2: + d1 = data[off + dataLength] & 0xff; + d2 = data[off + dataLength + 1] & 0xff; + + b1 = (d1 >>> 2) & 0x3f; + b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f; + b3 = (d2 << 2) & 0x3f; + + out.write(encodingTable[b1]); + out.write(encodingTable[b2]); + out.write(encodingTable[b3]); + out.write(padding); + break; + } + + return (dataLength / 3) * 4 + ((modulus == 0) ? 0 : 4); + } + + private boolean ignore( + char c) + { + return (c == '\n' || c =='\r' || c == '\t' || c == ' '); + } + + /** + * decode the base 64 encoded byte data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public int decode( + byte[] data, + int off, + int length, + OutputStream out) + throws IOException + { + byte b1, b2, b3, b4; + int outLen = 0; + + int end = off + length; + + while (end > off) + { + if (!ignore((char)data[end - 1])) + { + break; + } + + end--; + } + + int i = off; + int finish = end - 4; + + i = nextI(data, i, finish); + + while (i < finish) + { + b1 = decodingTable[data[i++]]; + + i = nextI(data, i, finish); + + b2 = decodingTable[data[i++]]; + + i = nextI(data, i, finish); + + b3 = decodingTable[data[i++]]; + + i = nextI(data, i, finish); + + b4 = decodingTable[data[i++]]; + + out.write((b1 << 2) | (b2 >> 4)); + out.write((b2 << 4) | (b3 >> 2)); + out.write((b3 << 6) | b4); + + outLen += 3; + + i = nextI(data, i, finish); + } + + outLen += decodeLastBlock(out, (char)data[end - 4], (char)data[end - 3], (char)data[end - 2], (char)data[end - 1]); + + return outLen; + } + + private int nextI(byte[] data, int i, int finish) + { + while ((i < finish) && ignore((char)data[i])) + { + i++; + } + return i; + } + + /** + * decode the base 64 encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public int decode( + String data, + OutputStream out) + throws IOException + { + byte b1, b2, b3, b4; + int length = 0; + + int end = data.length(); + + while (end > 0) + { + if (!ignore(data.charAt(end - 1))) + { + break; + } + + end--; + } + + int i = 0; + int finish = end - 4; + + i = nextI(data, i, finish); + + while (i < finish) + { + b1 = decodingTable[data.charAt(i++)]; + + i = nextI(data, i, finish); + + b2 = decodingTable[data.charAt(i++)]; + + i = nextI(data, i, finish); + + b3 = decodingTable[data.charAt(i++)]; + + i = nextI(data, i, finish); + + b4 = decodingTable[data.charAt(i++)]; + + out.write((b1 << 2) | (b2 >> 4)); + out.write((b2 << 4) | (b3 >> 2)); + out.write((b3 << 6) | b4); + + length += 3; + + i = nextI(data, i, finish); + } + + length += decodeLastBlock(out, data.charAt(end - 4), data.charAt(end - 3), data.charAt(end - 2), data.charAt(end - 1)); + + return length; + } + + private int decodeLastBlock(OutputStream out, char c1, char c2, char c3, char c4) + throws IOException + { + byte b1, b2, b3, b4; + + if (c3 == padding) + { + b1 = decodingTable[c1]; + b2 = decodingTable[c2]; + + out.write((b1 << 2) | (b2 >> 4)); + + return 1; + } + else if (c4 == padding) + { + b1 = decodingTable[c1]; + b2 = decodingTable[c2]; + b3 = decodingTable[c3]; + + out.write((b1 << 2) | (b2 >> 4)); + out.write((b2 << 4) | (b3 >> 2)); + + return 2; + } + else + { + b1 = decodingTable[c1]; + b2 = decodingTable[c2]; + b3 = decodingTable[c3]; + b4 = decodingTable[c4]; + + out.write((b1 << 2) | (b2 >> 4)); + out.write((b2 << 4) | (b3 >> 2)); + out.write((b3 << 6) | b4); + + return 3; + } + } + + private int nextI(String data, int i, int finish) + { + while ((i < finish) && ignore(data.charAt(i))) + { + i++; + } + return i; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/encoders/BufferedDecoder.java b/src/com/google/bitcoin/bouncycastle/util/encoders/BufferedDecoder.java new file mode 100644 index 000000000..0c8c2a414 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/encoders/BufferedDecoder.java @@ -0,0 +1,96 @@ +package com.google.bitcoin.bouncycastle.util.encoders; + + +/** + * a buffering class to allow translation from one format to another to + * be done in discrete chunks. + */ +public class BufferedDecoder +{ + protected byte[] buf; + protected int bufOff; + + protected Translator translator; + + /** + * @param translator the translator to use. + * @param bufSize amount of input to buffer for each chunk. + */ + public BufferedDecoder( + Translator translator, + int bufSize) + { + this.translator = translator; + + if ((bufSize % translator.getEncodedBlockSize()) != 0) + { + throw new IllegalArgumentException("buffer size not multiple of input block size"); + } + + buf = new byte[bufSize]; + bufOff = 0; + } + + public int processByte( + byte in, + byte[] out, + int outOff) + { + int resultLen = 0; + + buf[bufOff++] = in; + + if (bufOff == buf.length) + { + resultLen = translator.decode(buf, 0, buf.length, out, outOff); + bufOff = 0; + } + + return resultLen; + } + + public int processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int resultLen = 0; + int gapLen = buf.length - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += translator.decode(buf, 0, buf.length, out, outOff); + + bufOff = 0; + + len -= gapLen; + inOff += gapLen; + outOff += resultLen; + + int chunkSize = len - (len % buf.length); + + resultLen += translator.decode(in, inOff, chunkSize, out, outOff); + + len -= chunkSize; + inOff += chunkSize; + } + + if (len != 0) + { + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + return resultLen; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/encoders/BufferedEncoder.java b/src/com/google/bitcoin/bouncycastle/util/encoders/BufferedEncoder.java new file mode 100644 index 000000000..9612534a2 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/encoders/BufferedEncoder.java @@ -0,0 +1,96 @@ +package com.google.bitcoin.bouncycastle.util.encoders; + + +/** + * a buffering class to allow translation from one format to another to + * be done in discrete chunks. + */ +public class BufferedEncoder +{ + protected byte[] buf; + protected int bufOff; + + protected Translator translator; + + /** + * @param translator the translator to use. + * @param bufSize amount of input to buffer for each chunk. + */ + public BufferedEncoder( + Translator translator, + int bufSize) + { + this.translator = translator; + + if ((bufSize % translator.getEncodedBlockSize()) != 0) + { + throw new IllegalArgumentException("buffer size not multiple of input block size"); + } + + buf = new byte[bufSize]; + bufOff = 0; + } + + public int processByte( + byte in, + byte[] out, + int outOff) + { + int resultLen = 0; + + buf[bufOff++] = in; + + if (bufOff == buf.length) + { + resultLen = translator.encode(buf, 0, buf.length, out, outOff); + bufOff = 0; + } + + return resultLen; + } + + public int processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int resultLen = 0; + int gapLen = buf.length - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += translator.encode(buf, 0, buf.length, out, outOff); + + bufOff = 0; + + len -= gapLen; + inOff += gapLen; + outOff += resultLen; + + int chunkSize = len - (len % buf.length); + + resultLen += translator.encode(in, inOff, chunkSize, out, outOff); + + len -= chunkSize; + inOff += chunkSize; + } + + if (len != 0) + { + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + return resultLen; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/encoders/Encoder.java b/src/com/google/bitcoin/bouncycastle/util/encoders/Encoder.java new file mode 100644 index 000000000..67d28a75a --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/encoders/Encoder.java @@ -0,0 +1,17 @@ +package com.google.bitcoin.bouncycastle.util.encoders; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Encode and decode byte arrays (typically from binary to 7-bit ASCII + * encodings). + */ +public interface Encoder +{ + int encode(byte[] data, int off, int length, OutputStream out) throws IOException; + + int decode(byte[] data, int off, int length, OutputStream out) throws IOException; + + int decode(String data, OutputStream out) throws IOException; +} diff --git a/src/com/google/bitcoin/bouncycastle/util/encoders/Hex.java b/src/com/google/bitcoin/bouncycastle/util/encoders/Hex.java new file mode 100644 index 000000000..2e0c8f8f4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/encoders/Hex.java @@ -0,0 +1,131 @@ +package com.google.bitcoin.bouncycastle.util.encoders; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class Hex +{ + private static final Encoder encoder = new HexEncoder(); + + /** + * encode the input data producing a Hex encoded byte array. + * + * @return a byte array containing the Hex encoded data. + */ + public static byte[] encode( + byte[] data) + { + return encode(data, 0, data.length); + } + + /** + * encode the input data producing a Hex encoded byte array. + * + * @return a byte array containing the Hex encoded data. + */ + public static byte[] encode( + byte[] data, + int off, + int length) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + encoder.encode(data, off, length, bOut); + } + catch (IOException e) + { + throw new RuntimeException("exception encoding Hex string: " + e); + } + + return bOut.toByteArray(); + } + + /** + * Hex encode the byte data writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int encode( + byte[] data, + OutputStream out) + throws IOException + { + return encoder.encode(data, 0, data.length, out); + } + + /** + * Hex encode the byte data writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int encode( + byte[] data, + int off, + int length, + OutputStream out) + throws IOException + { + return encoder.encode(data, off, length, out); + } + + /** + * decode the Hex encoded input data. It is assumed the input data is valid. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + byte[] data) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + encoder.decode(data, 0, data.length, bOut); + } + catch (IOException e) + { + throw new RuntimeException("exception decoding Hex string: " + e); + } + + return bOut.toByteArray(); + } + + /** + * decode the Hex encoded String data - whitespace will be ignored. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + String data) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + encoder.decode(data, bOut); + } + catch (IOException e) + { + throw new RuntimeException("exception decoding Hex string: " + e); + } + + return bOut.toByteArray(); + } + + /** + * decode the Hex encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public static int decode( + String data, + OutputStream out) + throws IOException + { + return encoder.decode(data, out); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/encoders/HexEncoder.java b/src/com/google/bitcoin/bouncycastle/util/encoders/HexEncoder.java new file mode 100644 index 000000000..37caa3704 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/encoders/HexEncoder.java @@ -0,0 +1,172 @@ +package com.google.bitcoin.bouncycastle.util.encoders; + +import java.io.IOException; +import java.io.OutputStream; + +public class HexEncoder + implements Encoder +{ + protected final byte[] encodingTable = + { + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f' + }; + + /* + * set up the decoding table. + */ + protected final byte[] decodingTable = new byte[128]; + + protected void initialiseDecodingTable() + { + for (int i = 0; i < encodingTable.length; i++) + { + decodingTable[encodingTable[i]] = (byte)i; + } + + decodingTable['A'] = decodingTable['a']; + decodingTable['B'] = decodingTable['b']; + decodingTable['C'] = decodingTable['c']; + decodingTable['D'] = decodingTable['d']; + decodingTable['E'] = decodingTable['e']; + decodingTable['F'] = decodingTable['f']; + } + + public HexEncoder() + { + initialiseDecodingTable(); + } + + /** + * encode the input data producing a Hex output stream. + * + * @return the number of bytes produced. + */ + public int encode( + byte[] data, + int off, + int length, + OutputStream out) + throws IOException + { + for (int i = off; i < (off + length); i++) + { + int v = data[i] & 0xff; + + out.write(encodingTable[(v >>> 4)]); + out.write(encodingTable[v & 0xf]); + } + + return length * 2; + } + + private boolean ignore( + char c) + { + return (c == '\n' || c =='\r' || c == '\t' || c == ' '); + } + + /** + * decode the Hex encoded byte data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public int decode( + byte[] data, + int off, + int length, + OutputStream out) + throws IOException + { + byte b1, b2; + int outLen = 0; + + int end = off + length; + + while (end > off) + { + if (!ignore((char)data[end - 1])) + { + break; + } + + end--; + } + + int i = off; + while (i < end) + { + while (i < end && ignore((char)data[i])) + { + i++; + } + + b1 = decodingTable[data[i++]]; + + while (i < end && ignore((char)data[i])) + { + i++; + } + + b2 = decodingTable[data[i++]]; + + out.write((b1 << 4) | b2); + + outLen++; + } + + return outLen; + } + + /** + * decode the Hex encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public int decode( + String data, + OutputStream out) + throws IOException + { + byte b1, b2; + int length = 0; + + int end = data.length(); + + while (end > 0) + { + if (!ignore(data.charAt(end - 1))) + { + break; + } + + end--; + } + + int i = 0; + while (i < end) + { + while (i < end && ignore(data.charAt(i))) + { + i++; + } + + b1 = decodingTable[data.charAt(i++)]; + + while (i < end && ignore(data.charAt(i))) + { + i++; + } + + b2 = decodingTable[data.charAt(i++)]; + + out.write((b1 << 4) | b2); + + length++; + } + + return length; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/encoders/HexTranslator.java b/src/com/google/bitcoin/bouncycastle/util/encoders/HexTranslator.java new file mode 100644 index 000000000..4302e94a4 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/encoders/HexTranslator.java @@ -0,0 +1,87 @@ +package com.google.bitcoin.bouncycastle.util.encoders; + +/** + * Converters for going from hex to binary and back. Note: this class assumes ASCII processing. + */ +public class HexTranslator + implements Translator +{ + private static final byte[] hexTable = + { + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f' + }; + + /** + * size of the output block on encoding produced by getDecodedBlockSize() + * bytes. + */ + public int getEncodedBlockSize() + { + return 2; + } + + public int encode( + byte[] in, + int inOff, + int length, + byte[] out, + int outOff) + { + for (int i = 0, j = 0; i < length; i++, j += 2) + { + out[outOff + j] = hexTable[(in[inOff] >> 4) & 0x0f]; + out[outOff + j + 1] = hexTable[in[inOff] & 0x0f]; + + inOff++; + } + + return length * 2; + } + + /** + * size of the output block on decoding produced by getEncodedBlockSize() + * bytes. + */ + public int getDecodedBlockSize() + { + return 1; + } + + public int decode( + byte[] in, + int inOff, + int length, + byte[] out, + int outOff) + { + int halfLength = length / 2; + byte left, right; + for (int i = 0; i < halfLength; i++) + { + left = in[inOff + i * 2]; + right = in[inOff + i * 2 + 1]; + + if (left < (byte)'a') + { + out[outOff] = (byte)((left - '0') << 4); + } + else + { + out[outOff] = (byte)((left - 'a' + 10) << 4); + } + if (right < (byte)'a') + { + out[outOff] += (byte)(right - '0'); + } + else + { + out[outOff] += (byte)(right - 'a' + 10); + } + + outOff++; + } + + return halfLength; + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/encoders/Translator.java b/src/com/google/bitcoin/bouncycastle/util/encoders/Translator.java new file mode 100644 index 000000000..895e7fb94 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/encoders/Translator.java @@ -0,0 +1,23 @@ +package com.google.bitcoin.bouncycastle.util.encoders; + +/** + * general interface for an translator. + */ +public interface Translator +{ + /** + * size of the output block on encoding produced by getDecodedBlockSize() + * bytes. + */ + public int getEncodedBlockSize(); + + public int encode(byte[] in, int inOff, int length, byte[] out, int outOff); + + /** + * size of the output block on decoding produced by getEncodedBlockSize() + * bytes. + */ + public int getDecodedBlockSize(); + + public int decode(byte[] in, int inOff, int length, byte[] out, int outOff); +} diff --git a/src/com/google/bitcoin/bouncycastle/util/encoders/UrlBase64.java b/src/com/google/bitcoin/bouncycastle/util/encoders/UrlBase64.java new file mode 100644 index 000000000..8aad50074 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/encoders/UrlBase64.java @@ -0,0 +1,129 @@ +package com.google.bitcoin.bouncycastle.util.encoders; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Convert binary data to and from UrlBase64 encoding. This is identical to + * Base64 encoding, except that the padding character is "." and the other + * non-alphanumeric characters are "-" and "_" instead of "+" and "/". + *

    + * The purpose of UrlBase64 encoding is to provide a compact encoding of binary + * data that is safe for use as an URL parameter. Base64 encoding does not + * produce encoded values that are safe for use in URLs, since "/" can be + * interpreted as a path delimiter; "+" is the encoded form of a space; and + * "=" is used to separate a name from the corresponding value in an URL + * parameter. + */ +public class UrlBase64 +{ + private static final Encoder encoder = new UrlBase64Encoder(); + + /** + * Encode the input data producing a URL safe base 64 encoded byte array. + * + * @return a byte array containing the URL safe base 64 encoded data. + */ + public static byte[] encode( + byte[] data) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + encoder.encode(data, 0, data.length, bOut); + } + catch (IOException e) + { + throw new RuntimeException("exception encoding URL safe base64 string: " + e); + } + + return bOut.toByteArray(); + } + + /** + * Encode the byte data writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int encode( + byte[] data, + OutputStream out) + throws IOException + { + return encoder.encode(data, 0, data.length, out); + } + + /** + * Decode the URL safe base 64 encoded input data - white space will be ignored. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + byte[] data) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + encoder.decode(data, 0, data.length, bOut); + } + catch (IOException e) + { + throw new RuntimeException("exception decoding URL safe base64 string: " + e); + } + + return bOut.toByteArray(); + } + + /** + * decode the URL safe base 64 encoded byte data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public static int decode( + byte[] data, + OutputStream out) + throws IOException + { + return encoder.decode(data, 0, data.length, out); + } + + /** + * decode the URL safe base 64 encoded String data - whitespace will be ignored. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + String data) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + encoder.decode(data, bOut); + } + catch (IOException e) + { + throw new RuntimeException("exception decoding URL safe base64 string: " + e); + } + + return bOut.toByteArray(); + } + + /** + * Decode the URL safe base 64 encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public static int decode( + String data, + OutputStream out) + throws IOException + { + return encoder.decode(data, out); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/encoders/UrlBase64Encoder.java b/src/com/google/bitcoin/bouncycastle/util/encoders/UrlBase64Encoder.java new file mode 100644 index 000000000..96d321a32 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/encoders/UrlBase64Encoder.java @@ -0,0 +1,25 @@ +package com.google.bitcoin.bouncycastle.util.encoders; + +/** + * Convert binary data to and from UrlBase64 encoding. This is identical to + * Base64 encoding, except that the padding character is "." and the other + * non-alphanumeric characters are "-" and "_" instead of "+" and "/". + *

    + * The purpose of UrlBase64 encoding is to provide a compact encoding of binary + * data that is safe for use as an URL parameter. Base64 encoding does not + * produce encoded values that are safe for use in URLs, since "/" can be + * interpreted as a path delimiter; "+" is the encoded form of a space; and + * "=" is used to separate a name from the corresponding value in an URL + * parameter. + */ +public class UrlBase64Encoder extends Base64Encoder +{ + public UrlBase64Encoder() + { + encodingTable[encodingTable.length - 2] = (byte) '-'; + encodingTable[encodingTable.length - 1] = (byte) '_'; + padding = (byte) '.'; + // we must re-create the decoding table with the new encoded values. + initialiseDecodingTable(); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/io/StreamOverflowException.java b/src/com/google/bitcoin/bouncycastle/util/io/StreamOverflowException.java new file mode 100644 index 000000000..e3d9e9c81 --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/io/StreamOverflowException.java @@ -0,0 +1,12 @@ +package com.google.bitcoin.bouncycastle.util.io; + +import java.io.IOException; + +public class StreamOverflowException + extends IOException +{ + public StreamOverflowException(String msg) + { + super(msg); + } +} diff --git a/src/com/google/bitcoin/bouncycastle/util/io/Streams.java b/src/com/google/bitcoin/bouncycastle/util/io/Streams.java new file mode 100644 index 000000000..7af45282e --- /dev/null +++ b/src/com/google/bitcoin/bouncycastle/util/io/Streams.java @@ -0,0 +1,87 @@ +package com.google.bitcoin.bouncycastle.util.io; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public final class Streams +{ + private static int BUFFER_SIZE = 512; + + public static void drain(InputStream inStr) + throws IOException + { + byte[] bs = new byte[BUFFER_SIZE]; + while (inStr.read(bs, 0, bs.length) >= 0) + { + } + } + + public static byte[] readAll(InputStream inStr) + throws IOException + { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + pipeAll(inStr, buf); + return buf.toByteArray(); + } + + public static byte[] readAllLimited(InputStream inStr, int limit) + throws IOException + { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + pipeAllLimited(inStr, limit, buf); + return buf.toByteArray(); + } + + public static int readFully(InputStream inStr, byte[] buf) + throws IOException + { + return readFully(inStr, buf, 0, buf.length); + } + + public static int readFully(InputStream inStr, byte[] buf, int off, int len) + throws IOException + { + int totalRead = 0; + while (totalRead < len) + { + int numRead = inStr.read(buf, off + totalRead, len - totalRead); + if (numRead < 0) + { + break; + } + totalRead += numRead; + } + return totalRead; + } + + public static void pipeAll(InputStream inStr, OutputStream outStr) + throws IOException + { + byte[] bs = new byte[BUFFER_SIZE]; + int numRead; + while ((numRead = inStr.read(bs, 0, bs.length)) >= 0) + { + outStr.write(bs, 0, numRead); + } + } + + public static long pipeAllLimited(InputStream inStr, long limit, OutputStream outStr) + throws IOException + { + long total = 0; + byte[] bs = new byte[BUFFER_SIZE]; + int numRead; + while ((numRead = inStr.read(bs, 0, bs.length)) >= 0) + { + total += numRead; + if (total > limit) + { + throw new StreamOverflowException("Data Overflow"); + } + outStr.write(bs, 0, numRead); + } + return total; + } +} diff --git a/src/com/google/bitcoin/core/Address.java b/src/com/google/bitcoin/core/Address.java new file mode 100644 index 000000000..32e719729 --- /dev/null +++ b/src/com/google/bitcoin/core/Address.java @@ -0,0 +1,111 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import java.util.Arrays; + +/** + * A BitCoin address is fundamentally derived from an elliptic curve public key and a set of network parameters. + * It has several possible representations:

    + * + *

      + *
    1. The raw public key bytes themselves. + *
    2. RIPEMD160 hash of the public key bytes. + *
    3. A base58 encoded "human form" that includes a version and check code, to guard against typos. + *

    + * + * One may question whether the base58 form is really an improvement over the hash160 form, given + * they are both very unfriendly for typists. More useful representations might include qrcodes + * and identicons.

    + * + * Note that an address is specific to a network because the first byte is a discriminator value. + */ +public class Address { + private byte[] hash160; + private NetworkParameters params; + + /** + * Construct an address from parameters and the hash160 form. Example:

    + * + *

    new Address(NetworkParameters.prodNet(), Hex.decode("4a22c3c4cbb31e4d03b15550636762bda0baf85a"));
    + */ + public Address(NetworkParameters params, byte[] hash160) { + assert hash160.length == 20; + this.hash160 = hash160; + this.params = params; + } + + /** + * Construct an address from parameters and the standard "human readable" form. Example:

    + * + *

    new Address(NetworkParameters.prodNet(), "17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL");
    + */ + public Address(NetworkParameters params, String address) throws AddressFormatException { + this.params = params; + this.hash160 = strToHash160(address); + } + + /** The (big endian) 20 byte hash that is the core of a BitCoin address. */ + public byte[] getHash160() { + assert hash160 != null; + return hash160; + } + + + private byte[] strToHash160(String address) throws AddressFormatException { + byte[] bytes = Base58.decode(address); + if (bytes.length != 25) { + // Zero pad the result. + byte[] tmp = new byte[25]; + System.arraycopy(bytes, 0, tmp, tmp.length - bytes.length, bytes.length); + bytes = tmp; + } + if (bytes[0] != params.addressHeader) + throw new AddressFormatException(); + byte[] check = Utils.doubleDigest(bytes, 0, 21); + if (check[0] != bytes[21] || check[1] != bytes[22] || check[2] != bytes[23] || check[3] != bytes[24]) + throw new AddressFormatException(); + byte[] hash160 = new byte[20]; + System.arraycopy(bytes, 1, hash160, 0, 20); + return hash160; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Address)) return false; + Address a = (Address) o; + return Arrays.equals(a.getHash160(), getHash160()); + } + + @Override + public int hashCode() { + return Arrays.hashCode(getHash160()); + } + + @Override + public String toString() { + byte[] input = hash160; + // A stringified address is: + // 1 byte version + 20 bytes hash + 4 bytes check code (itself a truncated hash) + byte[] addressBytes = new byte[1 + 20 + 4]; + addressBytes[0] = params.addressHeader; + System.arraycopy(input, 0, addressBytes, 1, 20); + byte[] check = Utils.doubleDigest(addressBytes, 0, 21); + System.arraycopy(check, 0, addressBytes, 21, 4); + return Base58.encode(addressBytes); + } +} diff --git a/src/com/google/bitcoin/core/AddressFormatException.java b/src/com/google/bitcoin/core/AddressFormatException.java new file mode 100644 index 000000000..2dac32530 --- /dev/null +++ b/src/com/google/bitcoin/core/AddressFormatException.java @@ -0,0 +1,28 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +@SuppressWarnings("serial") +public class AddressFormatException extends Exception { + public AddressFormatException() { + super(); + } + + public AddressFormatException(String message) { + super(message); + } +} diff --git a/src/com/google/bitcoin/core/AddressMessage.java b/src/com/google/bitcoin/core/AddressMessage.java new file mode 100644 index 000000000..ba88def8e --- /dev/null +++ b/src/com/google/bitcoin/core/AddressMessage.java @@ -0,0 +1,42 @@ +package com.google.bitcoin.core; + +import java.util.ArrayList; +import java.util.List; + +public class AddressMessage extends Message { + private static final long MAX_ADDRESSES = 1024; + List addresses; + + AddressMessage(NetworkParameters params, byte[] payload, int offset) throws ProtocolException { + super(params, payload, offset); + } + + AddressMessage(NetworkParameters params, byte[] payload) throws ProtocolException { + super(params, payload, 0); + } + + @Override + void parse() throws ProtocolException { + long numAddresses = readVarInt(); + // Guard against ultra large messages that will crash us. + if (numAddresses > MAX_ADDRESSES) + throw new ProtocolException("Address message too large."); + addresses = new ArrayList((int)numAddresses); + for (int i = 0; i < numAddresses; i++) { + PeerAddress addr = new PeerAddress(params, bytes, cursor); + addresses.add(addr); + cursor += addr.getMessageSize(); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("addr: "); + for (PeerAddress a : addresses) { + builder.append(a.toString()); + builder.append(" "); + } + return builder.toString(); + } +} diff --git a/src/com/google/bitcoin/core/Base58.java b/src/com/google/bitcoin/core/Base58.java new file mode 100644 index 000000000..a2226a65d --- /dev/null +++ b/src/com/google/bitcoin/core/Base58.java @@ -0,0 +1,73 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import java.math.BigInteger; + +/** + * A custom form of base58 is used to encode BitCoin addresses. Note that this is not the same base58 as used by + * Flickr, which you may see reference to around the internet.

    + * + * Satoshi says: why base-58 instead of standard base-64 encoding?

    + * + *

      + *
    • Don't want 0OIl characters that look the same in some fonts and + * could be used to create visually identical looking account numbers.
    • + *
    • A string with non-alphanumeric characters is not as easily accepted as an account number.
    • + *
    • E-mail usually won't line-break if there's no punctuation to break at.
    • + *
    • Doubleclicking selects the whole number as one word if it's all alphanumeric.
    • + *
    + */ +public class Base58 { + + private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + private static final BigInteger BASE = BigInteger.valueOf(58); + + public static String encode(byte[] input) { + // TODO: This could be a lot more efficient. + BigInteger bi = new BigInteger(1, input); + StringBuffer s = new StringBuffer(); + while (bi.compareTo(BASE) >= 0) { + BigInteger mod = bi.mod(BASE); + s.insert(0, ALPHABET.charAt(mod.intValue())); + bi = bi.subtract(mod).divide(BASE); + } + s.insert(0, ALPHABET.charAt(bi.intValue())); + // Convert leading zeros too. + for (int i = 0; i < input.length; i++) { + if (input[i] == 0) + s.insert(0, ALPHABET.charAt(0)); + else + break; + } + return s.toString(); + } + + public static byte[] decode(String input) { + return decodeToBigInteger(input).toByteArray(); + } + + public static BigInteger decodeToBigInteger(String input) { + BigInteger bi = BigInteger.valueOf(0); + // Work backwards through the string. + for (int i = input.length() - 1; i >= 0; i--) { + int alphaIndex = ALPHABET.indexOf(input.charAt(i)); + bi = bi.add(BigInteger.valueOf(alphaIndex).multiply(BASE.pow(input.length() - 1 - i))); + } + return bi; + } +} diff --git a/src/com/google/bitcoin/core/Block.java b/src/com/google/bitcoin/core/Block.java new file mode 100644 index 000000000..303b786e6 --- /dev/null +++ b/src/com/google/bitcoin/core/Block.java @@ -0,0 +1,313 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.util.*; + +import static com.google.bitcoin.core.Utils.*; + +/** + * A block is the foundation of the BitCoin system. It records a set of {@link Transaction}s together with + * some data that links it into a place in the global block chain, and proves that a difficult calculation was done + * over its contents. See the BitCoin technical paper for more detail on blocks. + * + * To get a block, you can either build one from the raw bytes you can get from another implementation, or more likely + * you grab it from a downloaded {@link BlockChain}. + */ +public class Block extends Message { + public static final long ALLOWED_TIME_DRIFT = 2 * 60 * 60; // Same value as official client. + + long version; + byte[] prevBlockHash; + byte[] merkleRoot; + long time; + long difficultyTarget; // "nBits" + long nonce; + + // If null, it means this object holds only the headers. + List transactions; + byte[] hash; + + // If set, points towards the previous block in the chain. Note that a block may have multiple other blocks + // pointing back to it because despite being called a "chain", the block chain is in fact a tree. There can be + // splits which are resolved by selecting the head that has the largest total cumulative work when measured down + // to the genesis block. + Block prevBlock; + + /** Special case constructor, used for the genesis node and unit tests. */ + Block(NetworkParameters params) { + super(params); + // Set up a few basic things. We are not complete after this though. + version = 1; + difficultyTarget = 0x1d07fff8L; + time = System.currentTimeMillis() / 1000; + prevBlockHash = new byte[32]; // All zeros. + } + + /** Constructs a block object from the BitCoin wire format. */ + public Block(NetworkParameters params, byte[] payloadBytes) throws ProtocolException { + super(params, payloadBytes, 0); + } + + void parse() throws ProtocolException { + version = readUint32(); + prevBlockHash = readHash(); + merkleRoot = readHash(); + time = readUint32(); + difficultyTarget = readUint32(); + nonce = readUint32(); + + hash = Utils.reverseBytes(Utils.doubleDigest(bytes, 0, cursor)); + + int numTransactions = (int) readVarInt(); + transactions = new ArrayList(numTransactions); + for (int i = 0; i < numTransactions; i++) { + Transaction tx = new Transaction(params, bytes, cursor); + transactions.add(tx); + cursor += tx.getMessageSize(); + } + } + + private void writeHeader(OutputStream stream) throws IOException { + Utils.uint32ToByteStreamLE(version, stream); + stream.write(Utils.reverseBytes(prevBlockHash)); + stream.write(Utils.reverseBytes(getMerkleRoot())); + Utils.uint32ToByteStreamLE(time, stream); + Utils.uint32ToByteStreamLE(difficultyTarget, stream); + Utils.uint32ToByteStreamLE(nonce, stream); + } + + @Override + void bitcoinSerializeToStream(OutputStream stream) throws IOException { + writeHeader(stream); + // We may only have enough data to write the header. + if (transactions == null) return; + stream.write(new VarInt(transactions.size()).encode()); + for (Transaction tx : transactions) { + tx.bitcoinSerializeToStream(stream); + } + } + + /** Returns hash in little endian form */ + private byte[] calculateHash() { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + writeHeader(bos); + return Utils.reverseBytes(doubleDigest(bos.toByteArray())); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + + /** + * Returns the hash of the block (which for a valid, solved block should be below the target) in the form seen + * on the block explorer. If you call this on block 1 in the production chain, you will get + * "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048". + */ + public String getHashAsString() { + return Utils.bytesToHexString(getHash()); + } + + /** + * Returns the hash of the block (which for a valid, solved block should be below the target). Big endian. + */ + public byte[] getHash() { + if (hash == null) + hash = calculateHash(); + return hash; + } + + /** + * Returns a multi-line string containing a description of the contents of the block. Use for debugging purposes + * only. + */ + @Override + public String toString() { + StringBuffer s = new StringBuffer("v" + version + " block: \n" + + " previous block: " + bytesToHexString(prevBlockHash) + "\n" + + " merkle root: " + bytesToHexString(getMerkleRoot()) + "\n" + + " time: [" + time + "] " + new Date(time * 1000).toString() + "\n" + + " difficulty target (nBits): " + difficultyTarget + "\n" + + " nonce: " + nonce + "\n"); + if (transactions != null && transactions.size() > 0) { + s.append(" with ").append(transactions.size()).append(" transaction(s):\n"); + for (Transaction tx : transactions) { + s.append(tx.toString()); + } + } + return s.toString(); + } + + private void checkProofOfWork() throws VerificationException { + // This part is key - it is what proves the block was as difficult to make as it claims + // to be. Note however that in the context of this function, the block can claim to be + // as difficult as it wants to be .... if somebody was able to take control of our network + // connection and fork us onto a different chain, they could send us valid blocks with + // ridiculously easy difficulty and this function would accept them. + // + // To prevent this attack from being possible, elsewhere we check that the difficultyTarget + // field is of the right value. This requires us to have the preceeding blocks. + BigInteger target = Utils.decodeCompactBits(difficultyTarget); + + if (target.compareTo(BigInteger.valueOf(0)) <= 0 || target.compareTo(params.proofOfWorkLimit) > 0) + throw new VerificationException("Difficulty target is bad"); + + byte[] hashBytes = (hash == null ? calculateHash() : hash); + BigInteger h = new BigInteger(1, hashBytes); + if (h.compareTo(target) > 0) + throw new VerificationException("Hash is higher than target: " + bytesToHexString(hashBytes) + " vs " + + target.toString(16)); + } + + private void checkTimestamp() throws VerificationException { + if (time > (System.currentTimeMillis() / 1000) + ALLOWED_TIME_DRIFT) + throw new VerificationException("Block too far in future"); + } + + private void checkMerkleHash() throws VerificationException { + List tree = buildMerkleTree(); + byte[] calculatedRoot = tree.get(tree.size() - 1); + if (!Arrays.equals(calculatedRoot, merkleRoot)) { + LOG("Merkle tree did not verify: "); + for (byte[] b : tree) LOG(Utils.bytesToHexString(b)); + + throw new VerificationException("Merkle hashes do not match: " + + bytesToHexString(calculatedRoot) + " vs " + bytesToHexString(merkleRoot)); + } + } + + private byte[] calculateMerkleRoot() { + List tree = buildMerkleTree(); + return tree.get(tree.size() - 1); + } + + private List buildMerkleTree() { + // The merkle hash is based on a tree of hashes calculated from the transactions: + // + // merkleHash + // /\ + // / \ + // A B + // / \ / \ + // tx1 tx2 tx3 tx4 + // + // Basically transactions are hashed, then the hashes of the transactions are hashed + // again and so on upwards into the tree. The point of this scheme is to allow for + // disk space savings later on. + // + // This function is a direct translation of CBlock::BuildMerkleTree(). + ArrayList tree = new ArrayList(); + // Start by adding all the hashes of the transactions as leaves of the tree. + for (Transaction t : transactions) { + tree.add(t.getHash()); + } + int j = 0; + // Now step through each level ... + for (int size = transactions.size(); size > 1; size = (size + 1) / 2) { + // and for each leaf on that level .. + for (int i = 0; i < size; i += 2) { + int i2 = Math.min(i + 1, size - 1); + byte[] a = Utils.reverseBytes(tree.get(j + i)); + byte[] b = Utils.reverseBytes(tree.get(j + i2)); + tree.add(Utils.reverseBytes(doubleDigestTwoBuffers(a, 0, 32, b, 0, 32))); + } + j += size; + } + return tree; + } + + private void checkTransactions() throws VerificationException { + // The first transaction in a block must always be a coinbase transaction. + if (!transactions.get(0).isCoinBase()) + throw new VerificationException("First tx is not coinbase"); + // The rest must not be. + for (int i = 1; i < transactions.size(); i++) { + if (transactions.get(i).isCoinBase()) + throw new VerificationException("TX " + i + " is coinbase when it should not be."); + } + } + + /** + * Checks the block data to ensure it follows the rules laid out in the network parameters. Specifically, throws + * an exception if the proof of work is invalid, if the timestamp is too far from what it should be, or if the + * transactions don't hash to the value in the merkle root field. This is not everything that is required + * for a block to be valid, only what is checkable independent of the chain. + * + * @throws VerificationException + */ + public void verify() throws VerificationException { + // Now we need to prove that this block is OK. It might seem that we can just ignore + // most of these checks, given that the network is also verifying the blocks, but we + // cannot as it'd open us to a variety of obscure attacks. + // + // Firstly we need to ensure this block does in fact represent real work done. If the + // difficulty is high enough, it's probably been done by the network. + checkProofOfWork(); + checkTimestamp(); + // Now we need to check that the body of the block actually matches the headers. The + // network won't generate an invalid block, but if we didn't validate this then an + // untrusted man-in-the-middle could obtain the next valid block from the network and + // simply replace the transactions in it with their own fictional transactions that + // reference spent or non-existant inputs. + if (transactions != null) { + assert transactions.size() > 0; + //checkTransactions(); + checkMerkleHash(); + } + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Block)) return false; + Block other = (Block) o; + if (hash != null && other.hash != null) + return Arrays.equals(hash, other.hash); + // Otherwise we have to do it the slow way. + byte[] me = bitcoinSerialize(); + byte[] them = other.bitcoinSerialize(); + return Arrays.equals(me, them); + } + + @Override + public int hashCode() { + return Arrays.hashCode(hash); + } + + /** Returns the merkle root in big endian form, calculating it from transactions if necessary. */ + public byte[] getMerkleRoot() { + if (merkleRoot == null) + merkleRoot = calculateMerkleRoot(); + return merkleRoot; + } + + /** + * Adds a transaction to this block. + */ + public void addTransaction(Transaction t) { + if (transactions == null) { + transactions = new ArrayList(); + } + transactions.add(t); + // Force a recalculation next time the values are needed. + merkleRoot = null; + hash = null; + } +} diff --git a/src/com/google/bitcoin/core/BlockChain.java b/src/com/google/bitcoin/core/BlockChain.java new file mode 100644 index 000000000..889fefab1 --- /dev/null +++ b/src/com/google/bitcoin/core/BlockChain.java @@ -0,0 +1,247 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; + +import static com.google.bitcoin.core.Utils.LOG; +import static com.google.bitcoin.core.Utils.bytesToHexString; + +/** + * A BlockChain holds a series of {@link Block} objects, links them together, and knows how to verify that the + * chain follows the rules of the {@link NetworkParameters} for this chain.

    + * + * A BlockChain requires a {@link Wallet} to receive transactions that it finds during the initial download. However, + * if you don't care about this, you can just pass in an empty wallet and nothing bad will happen.

    + * + * A newly constructed BlockChain is empty. To fill it up, use a {@link Peer} object to download the chain from the + * network.

    + * + * Notes

    + * + * The 'chain' can actually be a tree although in normal operation it can be thought of as a simple list. In such a + * situation there are multiple stories of the economy competing to become the one true consensus. This can happen + * naturally when two miners solve a block within a few seconds of each other, or it can happen when the chain is + * under attack.

    + * + * A reference to the head block of every chain is stored. If you can reach the genesis block by repeatedly walking + * through the prevBlock pointers, then we say this is a full chain. If you cannot reach the genesis block we say it is + * an orphan chain.

    + * + * Orphan chains can occur when blocks are solved and received during the initial block chain download, + * or if we connect to a peer that doesn't send us blocks in order. + */ +public class BlockChain { + // This is going away. + private final LinkedList blockChain = new LinkedList(); + + /** Each chain head that we saw so far. */ + private final ArrayList chainHeads = new ArrayList(); + + private final NetworkParameters params; + private final Wallet wallet; + + // Holds blocks that we have received but can't plug into the chain yet, eg because they were created whilst we + // were downloading the block chain. + private final ArrayList unconnectedBlocks = new ArrayList(); + + public BlockChain(NetworkParameters params, Wallet wallet) { + blockChain.add(params.genesisBlock); + this.params = params; + this.wallet = wallet; + } + + /** + * Processes a received block and tries to add it to the chain. If there's something wrong with the block an + * exception is thrown. If the block is OK but cannot be connected to the chain at this time, returns false. + * If the block can be connected to the chain, returns true. + */ + public synchronized boolean add(Block block) throws VerificationException, ScriptException { + return add(block, true); + } + + private synchronized boolean add(Block block, boolean tryConnecting) throws VerificationException, ScriptException { + try { + block.verify(); + } catch (VerificationException e) { + LOG("Failed to verify block: " + e.toString()); + LOG(block.toString()); + throw e; + } + // If this block is a full block, scan, otherwise it's just headers (eg from getheaders or a unit test). + if (block.transactions != null) { + // Scan the transactions to find out if any sent money to us. We don't care about the rest. + // TODO: We should also scan to see if any of our own keys sent money to somebody else and became spent. + for (Transaction tx : block.transactions) { + try { + scanTransaction(tx); + } catch (ScriptException e) { + // We don't want scripts we don't understand to break the block chain, + // so just note that this tx was not scanned here and continue. + LOG("Failed to parse a script: " + e.toString()); + } + } + } + // We don't need the transaction data anymore. Free up some memory. + block.transactions = null; + // We know prev is OK because it's in the blockMap, that means we accepted it. + Block prev = blockChain.peekLast(); + if (prev.equals(block)) { + LOG("Re-received block that is currently on top of the chain."); + return true; + } + if (!Arrays.equals(block.prevBlockHash, prev.getHash())) { + // The block does not fit onto the top of the chain. It can either be: + // - Entirely unconnected. This can happen when a new block is solved and broadcast whilst we are in + // the process of downloading the block chain. + // - Connected to an earlier block in the chain than the top one. This can happen when there is a + // split in the chain. + // - Connected as part of an orphan chain, ie a chain of blocks that does not connect to the genesis + // block. + // TODO: We don't support most of these cases today and it's a high priority to do so. + unconnectedBlocks.add(block); + return false; + } + checkDifficultyTransitions(block); + // The block is OK so let's build the rest of the chain on it. + block.prevBlock = prev; + blockChain.add(block); + if (tryConnecting) + tryConnectingUnconnected(); + return true; + } + + /** + * For each block in unconnectedBlocks, see if we can now fit it on top of the chain and if so, do so. + */ + private void tryConnectingUnconnected() throws VerificationException, ScriptException { + // For each block in our unconnected list, try and fit it onto the head of the chain. If we succeed remove it + // from the list and keep going. If we changed the head of the list at the end of the round, + // try again until we can't fit anything else on the top. + int blocksConnectedThisRound; + do { + blocksConnectedThisRound = 0; + for (int i = 0; i < unconnectedBlocks.size(); i++) { + Block block = unconnectedBlocks.get(i); + if (Arrays.equals(block.prevBlockHash, blockChain.getLast().getHash())) { + // False here ensures we don't recurse infinitely downwards when connecting huge chains. + add(block, false); + unconnectedBlocks.remove(i); + i--; // The next iteration of the for loop will make "i" point to the right index again. + blocksConnectedThisRound++; + } + } + if (blocksConnectedThisRound > 0) { + LOG("Connected " + blocksConnectedThisRound + " floating blocks."); + } + } while (blocksConnectedThisRound > 0); + } + + static private final int TARGET_TIMESPAN = 14 * 24 * 60 * 60; + static private final int TARGET_SPACING = 10 * 60; + static private final int INTERVAL = TARGET_TIMESPAN / TARGET_SPACING; + + private void checkDifficultyTransitions(Block top) throws VerificationException { + Block prev = blockChain.peekLast(); + // Is this supposed to be a difficulty transition point? + if (blockChain.size() % INTERVAL != 0) { + // No ... so check the difficulty didn't actually change. + if (top.difficultyTarget != prev.difficultyTarget) + throw new VerificationException("Unexpected change in difficulty at height " + blockChain.size() + + ": " + Long.toHexString(top.difficultyTarget) + " vs " + Long.toHexString(prev.difficultyTarget)); + return; + } + + Block blockIntervalAgo = blockChain.get(blockChain.size() - INTERVAL); + int timespan = (int) (prev.time - blockIntervalAgo.time); + // Limit the adjustment step. + if (timespan < TARGET_TIMESPAN / 4) + timespan = TARGET_TIMESPAN / 4; + if (timespan > TARGET_TIMESPAN * 4) + timespan = TARGET_TIMESPAN * 4; + + BigInteger newDifficulty = Utils.decodeCompactBits(blockIntervalAgo.difficultyTarget); + newDifficulty = newDifficulty.multiply(BigInteger.valueOf(timespan)); + newDifficulty = newDifficulty.divide(BigInteger.valueOf(TARGET_TIMESPAN)); + + if (newDifficulty.compareTo(params.proofOfWorkLimit) > 0) { + newDifficulty = params.proofOfWorkLimit; + } + + int accuracyBytes = (int) (top.difficultyTarget >>> 24) - 3; + BigInteger receivedDifficulty = Utils.decodeCompactBits(top.difficultyTarget); + + // The calculated difficulty is to a higher precision than received, so reduce here. + BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8); + newDifficulty = newDifficulty.and(mask); + + if (newDifficulty.compareTo(receivedDifficulty) != 0) + throw new VerificationException("Calculated difficulty bits do not match what network provided: " + + receivedDifficulty.toString(16) + " vs " + newDifficulty.toString(16)); + } + + private void scanTransaction(Transaction tx) throws ScriptException, VerificationException { + if (tx.isCoinBase()) return; + for (TransactionOutput i : tx.outputs) { + if (i.getScriptPubKey().isSentToIP()) return; + byte[] pubKeyHash; + pubKeyHash = i.getScriptPubKey().getPubKeyHash(); + synchronized (wallet) { + for (ECKey key : wallet.keychain) { + if (Arrays.equals(pubKeyHash, key.getPubKeyHash())) { + // We found a transaction that sends us money. + if (!wallet.isTransactionPresent(tx)) + wallet.receive(tx); + } + } + } + } + for (TransactionInput i : tx.inputs) { + byte[] pubkey = i.getScriptSig().getPubKey(); + synchronized (wallet) { + for (ECKey key : wallet.keychain) { + if (Arrays.equals(pubkey, key.getPubKey())) { + // We found a transaction where we spent money. + if (wallet.isTransactionPresent(tx)) { + // TODO: Implement catching up with a set of pre-generated keys using the blockchain. + } + } + } + } + } + } + + /** + * Returns the highest known block or null if the chain is empty (top block is genesis). + */ + public synchronized Block getTopBlock() { + return blockChain.peekLast(); + } + + + /** + * Returns the most recent unconnected block or null if there are none. This will all have to change. + */ + public synchronized Block getUnconnectedBlock() { + if (unconnectedBlocks.size() == 0) + return null; + return unconnectedBlocks.get(unconnectedBlocks.size() - 1); + } +} diff --git a/src/com/google/bitcoin/core/ECKey.java b/src/com/google/bitcoin/core/ECKey.java new file mode 100644 index 000000000..edcaffe44 --- /dev/null +++ b/src/com/google/bitcoin/core/ECKey.java @@ -0,0 +1,198 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import com.google.bitcoin.bouncycastle.asn1.*; +import com.google.bitcoin.bouncycastle.asn1.sec.SECNamedCurves; +import com.google.bitcoin.bouncycastle.asn1.x9.X9ECParameters; +import com.google.bitcoin.bouncycastle.crypto.AsymmetricCipherKeyPair; +import com.google.bitcoin.bouncycastle.crypto.generators.ECKeyPairGenerator; +import com.google.bitcoin.bouncycastle.crypto.params.ECDomainParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECKeyGenerationParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPrivateKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.params.ECPublicKeyParameters; +import com.google.bitcoin.bouncycastle.crypto.signers.ECDSASigner; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.Serializable; +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * Represents either an elliptic curve keypair that we own and can use for signing transactions. Currently, + * Bouncy Castle is used. In future this may become an interface with multiple implementations using different crypto + * libraries. The class also provides a static method that can verify a signature with just the public key.

    + */ +public class ECKey implements Serializable { + private static final ECDomainParameters ecParams; + + private static final SecureRandom secureRandom; + private static final long serialVersionUID = -728224901792295832L; + + static { + // All clients must agree on the curve to use by agreement. BitCoin uses secp256k1. + X9ECParameters params = SECNamedCurves.getByName("secp256k1"); + ecParams = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); + secureRandom = new SecureRandom(); + } + + private final BigInteger priv; + private final byte[] pub; + + transient private byte[] pubKeyHash; + + /** Generates an entirely new keypair. */ + public ECKey() { + ECKeyPairGenerator generator = new ECKeyPairGenerator(); + ECKeyGenerationParameters keygenParams = new ECKeyGenerationParameters(ecParams, secureRandom); + generator.init(keygenParams); + AsymmetricCipherKeyPair keypair = generator.generateKeyPair(); + ECPrivateKeyParameters privParams = (ECPrivateKeyParameters) keypair.getPrivate(); + ECPublicKeyParameters pubParams = (ECPublicKeyParameters) keypair.getPublic(); + priv = privParams.getD(); + // The public key is an encoded point on the elliptic curve. It has no meaning independent of the curve. + pub = pubParams.getQ().getEncoded(); + } + + /** + * Construct an ECKey from an ASN.1 encoded private key. These are produced by OpenSSL and stored by the BitCoin + * reference implementation in its wallet. + */ + public static ECKey fromASN1(byte[] asn1privkey) { + return new ECKey(extractPrivateKeyFromASN1(asn1privkey)); + } + + /** + * Creates an ECKey given only the private key. This works because EC public keys are derivable from their + * private keys by doing a multiply with the generator value. + */ + public ECKey(BigInteger privKey) { + this.priv = privKey; + this.pub = publicKeyFromPrivate(privKey); + } + + /** Derive the public key by doing a point multiply of G * priv. */ + private static byte[] publicKeyFromPrivate(BigInteger privKey) { + return ecParams.getG().multiply(privKey).getEncoded(); + } + + /** Gets the hash160 form of the public key (as seen in addresses). */ + public byte[] getPubKeyHash() { + if (pubKeyHash == null) + pubKeyHash = Utils.sha256hash160(this.pub); + return pubKeyHash; + } + + /** Gets the raw public key value. */ + public byte[] getPubKey() { + return pub; + } + + public String toString() { + StringBuffer b = new StringBuffer(); + b.append("pub:").append(Utils.bytesToHexString(pub)); + b.append(" priv:").append(Utils.bytesToHexString(priv.toByteArray())); + return b.toString(); + } + + + public Address toAddress(NetworkParameters params) { + byte[] hash160 = Utils.sha256hash160(pub); + return new Address(params, hash160); + } + + /** + * Calcuates an ECDSA signature in DER format for the given input hash. Note that the input is expected to be + * 32 bytes long. + */ + public byte[] sign(byte[] input) { + ECDSASigner signer = new ECDSASigner(); + ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(priv, ecParams); + signer.init(true, privKey); + BigInteger[] sigs = signer.generateSignature(input); + // What we get back from the signer are the two components of a signature, r and s. To get a flat byte stream + // of the type used by BitCoin we have to encode them using DER encoding, which is just a way to pack the two + // components into a structure. + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DERSequenceGenerator seq = new DERSequenceGenerator(bos); + seq.addObject(new DERInteger(sigs[0])); + seq.addObject(new DERInteger(sigs[1])); + seq.close(); + return bos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + + /** + * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. + * @param data Hash of the data to verify. + * @param signature ASN.1 encoded signature. + * @param pub The public key bytes to use. + */ + public static boolean verify(byte[] data, byte[] signature, byte[] pub) { + ECDSASigner signer = new ECDSASigner(); + ECPublicKeyParameters params = new ECPublicKeyParameters(ecParams.getCurve().decodePoint(pub), ecParams); + signer.init(false, params); + try { + ASN1InputStream decoder = new ASN1InputStream(signature); + DERSequence seq = (DERSequence) decoder.readObject(); + DERInteger r = (DERInteger) seq.getObjectAt(0); + DERInteger s = (DERInteger) seq.getObjectAt(1); + decoder.close(); + return signer.verifySignature(data, r.getValue(), s.getValue()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. + * @param data Hash of the data to verify. + * @param signature ASN.1 encoded signature. + */ + public boolean verify(byte[] data, byte[] signature) { + return ECKey.verify(data, signature, pub); + } + + + private static BigInteger extractPrivateKeyFromASN1(byte[] asn1privkey) { + // To understand this code, see the definition of the ASN.1 format for EC private keys in the OpenSSL source + // code in ec_asn1.c: + // + // ASN1_SEQUENCE(EC_PRIVATEKEY) = { + // ASN1_SIMPLE(EC_PRIVATEKEY, version, LONG), + // ASN1_SIMPLE(EC_PRIVATEKEY, privateKey, ASN1_OCTET_STRING), + // ASN1_EXP_OPT(EC_PRIVATEKEY, parameters, ECPKPARAMETERS, 0), + // ASN1_EXP_OPT(EC_PRIVATEKEY, publicKey, ASN1_BIT_STRING, 1) + // } ASN1_SEQUENCE_END(EC_PRIVATEKEY) + // + try { + ASN1InputStream decoder = new ASN1InputStream(asn1privkey); + DERSequence seq = (DERSequence) decoder.readObject(); + assert seq.size() == 4 : "Input does not appear to be an ASN.1 OpenSSL EC private key"; + assert ((DERInteger) seq.getObjectAt(0)).getValue().equals(BigInteger.ONE) : "Input is of wrong version"; + DEROctetString key = (DEROctetString) seq.getObjectAt(1); + decoder.close(); + return new BigInteger(key.getOctets()); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen, reading from memory stream. + } + } +} diff --git a/src/com/google/bitcoin/core/GetBlocksMessage.java b/src/com/google/bitcoin/core/GetBlocksMessage.java new file mode 100644 index 000000000..eded5c3c0 --- /dev/null +++ b/src/com/google/bitcoin/core/GetBlocksMessage.java @@ -0,0 +1,66 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +public class GetBlocksMessage extends Message { + private final List locator; + private final byte[] stopHash; + + public GetBlocksMessage(NetworkParameters params, List locator, byte[] stopHash) { + super(params); + this.locator = locator; + this.stopHash = stopHash; + } + + public void parse() { + } + + public String toString() { + StringBuffer b = new StringBuffer(); + b.append("getblocks: "); + for (byte[] hash : locator) { + b.append(Utils.bytesToHexString(hash)); + b.append(" "); + } + return b.toString(); + } + + public byte[] bitcoinSerialize() { + try { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + // Version, for some reason. + Utils.uint32ToByteStreamLE(VersionMessage.PROTOCOL_VERSION, buf); + // Then a vector of block hashes. This is actually a "block locator", a set of block + // identifiers that spans the entire chain with exponentially increasing gaps between + // them, until we end up at the genesis block. See CBlockLocator::Set() + buf.write(new VarInt(locator.size()).encode()); + for (byte[] hash : locator) { + // Have to reverse as wire format is little endian. + buf.write(Utils.reverseBytes(hash)); + } + // Next, a block ID to stop at. + buf.write(stopHash); + return buf.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + } +} diff --git a/src/com/google/bitcoin/core/GetDataMessage.java b/src/com/google/bitcoin/core/GetDataMessage.java new file mode 100644 index 000000000..7b101d32e --- /dev/null +++ b/src/com/google/bitcoin/core/GetDataMessage.java @@ -0,0 +1,33 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +public class GetDataMessage extends Message { + public GetDataMessage(NetworkParameters params, byte[] payloadBytes) throws ProtocolException { + super(params, payloadBytes, 0); + } + + + public byte[] bitcoinSerialize() { + return new byte[] {}; + } + + @Override + public void parse() throws ProtocolException { + // TODO Auto-generated method stub + } +} diff --git a/src/com/google/bitcoin/core/InventoryItem.java b/src/com/google/bitcoin/core/InventoryItem.java new file mode 100644 index 000000000..532fdee74 --- /dev/null +++ b/src/com/google/bitcoin/core/InventoryItem.java @@ -0,0 +1,38 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +public class InventoryItem { + public enum Type { + Error, + Transaction, + Block + } + + public final Type type; + public final byte[] hash; + + public InventoryItem(Type type, byte[] hash) { + this.type = type; + this.hash = hash; + } + + + public String toString() { + return type.toString() + ": " + Utils.bytesToHexString(hash); + } +} diff --git a/src/com/google/bitcoin/core/InventoryMessage.java b/src/com/google/bitcoin/core/InventoryMessage.java new file mode 100644 index 000000000..2b9a07d22 --- /dev/null +++ b/src/com/google/bitcoin/core/InventoryMessage.java @@ -0,0 +1,78 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import com.google.bitcoin.core.InventoryItem.Type; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +public class InventoryMessage extends Message { + private static final long MAX_INVENTORY_ITEMS = 50000; + + // For some reason the compiler complains if this is inside InventoryItem + public List items; + + public InventoryMessage(NetworkParameters params, byte[] bytes) throws ProtocolException { + super(params, bytes, 0); + } + + @Override + public void parse() throws ProtocolException { + // An inv is vector where CInv is int+hash. The int is either 1 or 2 for tx or block. + long arrayLen = readVarInt(); + if (arrayLen > MAX_INVENTORY_ITEMS) + throw new ProtocolException("Too many items in INV message: " + arrayLen); + items = new ArrayList((int)arrayLen); + for (int i = 0; i < arrayLen; i++) { + if (cursor + 4 + 32 > bytes.length) { + throw new ProtocolException("Ran off the end of the INV"); + } + int typeCode = (int) readUint32(); + Type type; + // See ppszTypeName in net.h + switch (typeCode) { + case 0: type = InventoryItem.Type.Error; break; + case 1: type = InventoryItem.Type.Transaction; break; + case 2: type = InventoryItem.Type.Block; break; + default: + throw new ProtocolException("Unknown CInv type: " + typeCode); + } + InventoryItem item = new InventoryItem(type, readHash()); + items.add(item); + } + bytes = null; + } + + public InventoryMessage(NetworkParameters params) { + super(params); + items = new ArrayList(); + } + + @Override + public void bitcoinSerializeToStream( OutputStream stream) throws IOException { + stream.write(new VarInt(items.size()).encode()); + for (InventoryItem i : items) { + // Write out the type code. + Utils.uint32ToByteStreamLE(i.type.ordinal(), stream); + // And now the hash. + stream.write(Utils.reverseBytes(i.hash)); + } + } +} diff --git a/src/com/google/bitcoin/core/Message.java b/src/com/google/bitcoin/core/Message.java new file mode 100644 index 000000000..5341b44e8 --- /dev/null +++ b/src/com/google/bitcoin/core/Message.java @@ -0,0 +1,142 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Arrays; + +/** + * A Message is a data structure that can be serialized/deserialized using both the BitCoin proprietary serialization + * format and built-in Java object serialization. Specific types of messages that are used both in the block chain, + * and on the wire, are derived from this class. + * + * This class is not useful for library users. If you want to talk to the network see the {@link Peer} class. + */ +public abstract class Message implements Serializable { + public static final int MAX_SIZE = 0x02000000; + + // Useful to ensure serialize/deserialize are consistent with each other. + private static final boolean SELF_CHECK = false; + + // The offset is how many bytes into the provided byte array this message starts at. + protected transient int offset; + // The cursor keeps track of where we are in the byte array as we parse it. + // Note that it's relative to the start of the array NOT the start of the message. + protected transient int cursor; + + protected transient byte[] bytes; + + // This will be saved by subclasses that implement Serializable. + protected NetworkParameters params; + + /** This exists for the Java serialization framework to use only. */ + protected Message() { + } + + Message(NetworkParameters params) { + this.params = params; + } + + @SuppressWarnings("unused") + Message(NetworkParameters params, byte[] msg, int offset) throws ProtocolException { + this.params = params; + this.bytes = msg; + this.cursor = this.offset = offset; + parse(); + if (SELF_CHECK && !this.getClass().getSimpleName().equals("VersionMessage")) { + byte[] msgbytes = new byte[cursor - offset]; + System.arraycopy(msg, offset, msgbytes, 0, cursor - offset); + byte[] reserialized = bitcoinSerialize(); + if (!Arrays.equals(reserialized, msgbytes)) + throw new RuntimeException("Serialization is wrong: \n" + + Utils.bytesToHexString(reserialized) + " vs \n" + + Utils.bytesToHexString(msgbytes)); + } + this.bytes = null; + } + + // These methods handle the serialization/deserialization using the custom BitCoin protocol. + // It's somewhat painful to work with in Java, so some of these objects support a second + // serialization mechanism - the standard Java serialization system. This is used when things + // are serialized to the wallet. + abstract void parse() throws ProtocolException; + + public byte[] bitcoinSerialize() { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + bitcoinSerializeToStream(stream); + } catch (IOException e) { + // Cannot happen, we are serializing to a memory stream. + throw new RuntimeException(e); + } + return stream.toByteArray(); + } + + /** + * Serializes this message to the provided stream. If you just want the raw bytes use bitcoinSerialize(). + */ + void bitcoinSerializeToStream(OutputStream stream) throws IOException { + } + + int getMessageSize() { + return cursor - offset; + } + + long readUint32() { + long u = Utils.readUint32(bytes, cursor); + cursor += 4; + return u; + } + + byte[] readHash() { + byte[] hash = new byte[32]; + System.arraycopy(bytes, cursor, hash, 0, 32); + // We have to flip it around, as it's been read off the wire in little endian. + // Not the most efficient way to do this but the clearest. + hash = Utils.reverseBytes(hash); + cursor += 32; + return hash; + } + + + BigInteger readUint64() { + // Java does not have an unsigned 64 bit type. So scrape it off the wire then flip. + byte[] valbytes = new byte[8]; + System.arraycopy(bytes, cursor, valbytes, 0, 8); + valbytes = Utils.reverseBytes(valbytes); + cursor += valbytes.length; + return new BigInteger(valbytes); + } + + long readVarInt() { + VarInt varint = new VarInt(bytes, cursor); + cursor += varint.getSizeInBytes(); + return varint.value; + } + + + byte[] readBytes(int length) { + byte[] b = new byte[length]; + System.arraycopy(bytes, cursor, b, 0, length); + cursor += length; + return b; + } +} diff --git a/src/com/google/bitcoin/core/NetworkConnection.java b/src/com/google/bitcoin/core/NetworkConnection.java new file mode 100644 index 000000000..0d528ec37 --- /dev/null +++ b/src/com/google/bitcoin/core/NetworkConnection.java @@ -0,0 +1,260 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.net.Socket; + +import static com.google.bitcoin.core.Utils.*; + +/** + * A NetworkConnection handles talking to a remote BitCoin peer at a low level. It understands how to read and write + * messages off the network, but doesn't asynchronously communicate with the peer or handle the higher level details + * of the protocol. After constructing a NetworkConnection, use a {@link Peer} to hand off communication to a + * background thread. + * + * Construction is blocking whilst the protocol version is negotiated. + */ +public class NetworkConnection { + static final int COMMAND_LEN = 12; + + // Message strings. + static final String MSG_VERSION = "version"; + static final String MSG_INVENTORY = "inv"; + static final String MSG_BLOCK = "block"; + static final String MSG_GETBLOCKS = "getblocks"; + static final String MSG_GETDATA = "getdata"; + static final String MSG_TX = "tx"; + static final String MSG_ADDR = "addr"; + static final String MSG_VERACK = "verack"; + + private final Socket socket; + private final OutputStream out; + private final InputStream in; + + private boolean usesChecksumming; + private final NetworkParameters params; + static final private boolean PROTOCOL_LOG = false; + + /** + * Connect to the given IP address using the port specified as part of the network parameters. Once construction + * is complete a functioning network channel is set up and running. + * @param remoteIp IP address to connect to. IPv6 is not currently supported by BitCoin. + * @param params Defines which network to connect to and details of the protocol. + * @throws IOException if there is a network related failure. + * @throws ProtocolException if the version negotiation failed. + */ + public NetworkConnection(InetAddress remoteIp, NetworkParameters params) throws IOException, ProtocolException { + this.params = params; + socket = new Socket(remoteIp, params.port); + out = socket.getOutputStream(); + in = socket.getInputStream(); + + // When connecting, the remote peer sends us a version message with various bits of + // useful data in it. We need to know the peer protocol version before we can talk to it. + VersionMessage ver = (VersionMessage) readMessage(); + // Now it's our turn ... + writeMessage(MSG_VERSION, new VersionMessage(params)); + // Send an ACK message stating we accept the peers protocol version. + writeMessage(MSG_VERACK, new byte[] {}); + // And get one back ... + readMessage(); + // Switch to the new protocol version. + int peerVersion = (int) ver.clientVersion; + usesChecksumming = peerVersion >= 209; + // Handshake is done! + } + + /** + * Sends a "ping" message to the remote node. The protocol doesn't presently use this feature much. + * @throws IOException + */ + public void ping() throws IOException { + writeMessage("ping", new byte[] {}); + } + + /** + * Shuts down the network socket. Note that there's no way to wait for a socket to be fully flushed out to the + * wire, so if you call this immediately after sending a message it might not get sent. + */ + public void shutdown() throws IOException { + socket.shutdownOutput(); + socket.shutdownInput(); + socket.close(); + } + + /** + * Reads a network message from the wire, blocking until the message is fully received. + * + * @return An instance of a Message subclass. + * @throws ProtocolException if the message is badly formatted, failed checksum or there was a protocol failure. + */ + public Message readMessage() throws ProtocolException { + // A BitCoin protocol message has the following format. + // + // - 4 byte magic number: 0xfabfb5da for the testnet or + // 0xf9beb4d9 for production + // - 12 byte command in ASCII + // - 4 byte payload size + // - 4 byte checksum + // - Payload data + // + // The checksum is the first 4 bytes of a SHA256 hash of the message payload. It isn't + // present for all messages, notably, the first one on a connection. + byte[] header = new byte[4 + COMMAND_LEN + 4 + (usesChecksumming ? 4 : 0)]; + try { + int readCursor = 0; + while (readCursor < header.length) { + int bytesRead = in.read(header, readCursor, header.length - readCursor); + if (bytesRead == -1) { + throw new ProtocolException("Socket disconnected half way through a message"); + } + readCursor += bytesRead; + } + } catch (IOException e) { + throw new ProtocolException(e); + } + + int cursor = 0; + long magic = Utils.readUint32BE(header, 0); + cursor += 4; + if (magic != params.packetMagic) + throw new ProtocolException(String.format("Unexpected magic number: 0x%x", magic)); + + // The command is a NULL terminated string, unless the command fills all twelve bytes + // in which case the termination is implicit. + String command; + int mark = cursor; + for (; header[cursor] != 0 && cursor - mark < COMMAND_LEN; cursor++); + byte[] commandBytes = new byte[cursor - mark]; + System.arraycopy(header, mark, commandBytes, 0, cursor - mark); + try { + command = new String(commandBytes, "US-ASCII"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); // Cannot happen. + } + cursor = mark + COMMAND_LEN; + + int size = (int) readUint32(header, cursor); + cursor += 4; + + if (size > Message.MAX_SIZE) + throw new ProtocolException("Message size too large: " + size); + + if (PROTOCOL_LOG) + LOG("Received " + size + " byte '" + command + "' command"); + + // Old clients don't send the checksum. + byte[] checksum = new byte[4]; + if (usesChecksumming) { + // Note that the size read above includes the checksum bytes. + System.arraycopy(header, cursor, checksum, 0, 4); + cursor += 4; + } + + // Now try to read the whole message. + int readCursor = 0; + byte[] payloadBytes = new byte[size]; + try { + while (readCursor < payloadBytes.length - 1) { + int bytesRead = in.read(payloadBytes, readCursor, size - readCursor); + if (bytesRead == -1) { + throw new ProtocolException("Socket disconnected half way through a message"); + } + readCursor += bytesRead; + } + } catch (IOException e) { + throw new ProtocolException(e); + } + + // Verify the checksum. + if (usesChecksumming) { + byte[] hash = doubleDigest(payloadBytes); + if (checksum[0] != hash[0] || checksum[1] != hash[1] || + checksum[2] != hash[2] || checksum[3] != hash[3]) { + throw new ProtocolException("Checksum failed to verify, actual " + + bytesToHexString(hash) + + " vs " + bytesToHexString(checksum)); + } + } + + try { + Message message = null; + if (command.equals(MSG_VERSION)) + message = new VersionMessage(params, payloadBytes); + else if (command.equals(MSG_INVENTORY)) + message = new InventoryMessage(params, payloadBytes); + else if (command.equals(MSG_BLOCK)) + message = new Block(params, payloadBytes); + else if (command.equals(MSG_GETDATA)) + message = new GetDataMessage(params, payloadBytes); + else if (command.equals(MSG_TX)) + message = new Transaction(params, payloadBytes); + else if (command.equals(MSG_ADDR)) + message = new AddressMessage(params, payloadBytes); + else + message = new UnknownMessage(params, command, payloadBytes); + return message; + } catch (Exception e) { + throw new ProtocolException("Error deserializing message " + Utils.bytesToHexString(payloadBytes) + "\n", e); + } + } + + private void writeMessage(String name, byte[] payload) throws IOException { + byte[] header = new byte[4 + COMMAND_LEN + 4 + (usesChecksumming ? 4 : 0)]; + + uint32ToByteArrayBE(params.packetMagic, header, 0); + + // The header array is initialized to zero by Java so we don't have to worry about + // NULL terminating the string here. + for (int i = 0; i < name.length() && i < COMMAND_LEN; i++) { + header[4 + i] = (byte) (name.codePointAt(i) & 0xFF); + } + + Utils.uint32ToByteArrayLE(payload.length, header, 4 + COMMAND_LEN); + + if (usesChecksumming) { + byte[] hash = doubleDigest(payload); + System.arraycopy(hash, 0, header, 4 + COMMAND_LEN + 4, 4); + } + + if (PROTOCOL_LOG) + LOG("Sending " + name + " message: " + bytesToHexString(payload)); + + // Another writeMessage call may be running concurrently. + synchronized (out) { + out.write(header); + out.write(payload); + } + } + + /** + * Writes the given message out over the network using the protocol tag. For a Transaction + * this should be "tx" for example. It's safe to call this from multiple threads simultaneously, + * the actual writing will be serialized. + * + * @throws IOException + */ + public void writeMessage(String tag, Message message) throws IOException { + // TODO: Requiring "tag" here is redundant, the message object should know its own protocol tag. + writeMessage(tag, message.bitcoinSerialize()); + } +} diff --git a/src/com/google/bitcoin/core/NetworkParameters.java b/src/com/google/bitcoin/core/NetworkParameters.java new file mode 100644 index 000000000..cd7526289 --- /dev/null +++ b/src/com/google/bitcoin/core/NetworkParameters.java @@ -0,0 +1,104 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import com.google.bitcoin.bouncycastle.util.encoders.Hex; + +import java.io.ByteArrayOutputStream; +import java.io.Serializable; +import java.math.BigInteger; + +/** + * NetworkParameters contains the data needed for working with an instantiation of a BitCoin chain. + * + * Currently there are only two, the production chain and the test chain. But in future as BitCoin + * evolves there may be more. You can create your own as long as they don't conflict. + */ +public class NetworkParameters implements Serializable { + private static final long serialVersionUID = 2579833727976661964L; + + // TODO: Seed nodes and checkpoint values should be here as well. + + /** Genesis block for this chain */ + public Block genesisBlock; + /** What the easiest allowable proof of work should be. */ + public BigInteger proofOfWorkLimit; + /** Default TCP port on which to connect to nodes. */ + public int port; + /** The header bytes that identify the start of a packet on this network. */ + public long packetMagic; + /** First byte of a base58 encoded address. */ + public byte addressHeader; + + // The genesis block is the first block in the chain and is a shared, well known block of data containin a + // headline from the Times, as well as initialization values for that chain. The testnet uses a similar genesis + // block to the production network but with different times and nonces. + private static Block createGenesis(NetworkParameters n) { + Block genesisBlock = new Block(n); + Transaction t = new Transaction(n); + try { + // A script containing the difficulty bits and the following message: + // + // "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks" + byte[] bytes = Hex.decode + ("04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73"); + t.inputs.add(new TransactionInput(n, bytes)); + ByteArrayOutputStream scriptPubKeyBytes = new ByteArrayOutputStream(); + Script.writeBytes(scriptPubKeyBytes, Hex.decode + ("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f")); + scriptPubKeyBytes.write(Script.OP_CHECKSIG); + t.outputs.add(new TransactionOutput(n, scriptPubKeyBytes.toByteArray())); + } catch (Exception e) { + // Cannot happen. + } + genesisBlock.addTransaction(t); + return genesisBlock; + } + + /** The test chain created by Gavin. */ + public static NetworkParameters testNet() { + NetworkParameters n = new NetworkParameters(); + // Genesis hash is 0000000224b1593e3ff16a0e3b61285bbc393a39f78c8aa48c456142671f7110 + n.proofOfWorkLimit = new BigInteger("0000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16); + n.packetMagic = 0xfabfb5daL; + n.port = 18333; + n.addressHeader = 111; + n.genesisBlock = createGenesis(n); + n.genesisBlock.time = 1296688602L; + n.genesisBlock.difficultyTarget = 0x1d07fff8L; + n.genesisBlock.nonce = 384568319; + String genesisHash = n.genesisBlock.getHashAsString(); + assert genesisHash.equals("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"); + return n; + } + + /** The primary BitCoin chain created by Satoshi. */ + public static NetworkParameters prodNet() { + NetworkParameters n = new NetworkParameters(); + n.proofOfWorkLimit = new BigInteger("00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16); + n.port = 8333; + n.packetMagic = 0xf9beb4d9L; + n.addressHeader = 0; + n.genesisBlock = createGenesis(n); + n.genesisBlock.difficultyTarget = 0x1d00ffffL; + n.genesisBlock.time = 1231006505; + n.genesisBlock.nonce = 2083236893; + String genesisHash = n.genesisBlock.getHashAsString(); + assert genesisHash.equals("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); + return n; + } +} diff --git a/src/com/google/bitcoin/core/Peer.java b/src/com/google/bitcoin/core/Peer.java new file mode 100644 index 000000000..a651d12b1 --- /dev/null +++ b/src/com/google/bitcoin/core/Peer.java @@ -0,0 +1,355 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import java.io.IOException; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.*; + +import static com.google.bitcoin.core.Utils.LOG; + +/** + * A Peer handles the high level communication with a BitCoin node. It requires a NetworkConnection to be set up for + * it. After that it takes ownership of the connection, creates and manages its own thread used for communication + * with the network. All these threads synchronize on the block chain. + */ +public class Peer { + private final NetworkConnection conn; + private final NetworkParameters params; + private Thread thread; + private boolean running; + private final BlockChain blockChain; + + // Used to notify clients when the initial block chain download is finished. + private CountDownLatch chainCompletionLatch; + // When we want to download a block or transaction from a peer, the InventoryItem is put here whilst waiting for + // the response. Synchronized on itself. + private List pendingGetDataFutures; + + /** + * Construct a peer that handles the given network connection and reads/writes from the given block chain. Note that + * communication won't occur until you call start(). + */ + public Peer(NetworkParameters params, NetworkConnection conn, BlockChain blockChain) { + this.conn = conn; + this.params = params; + this.blockChain = blockChain; + this.pendingGetDataFutures = new ArrayList(); + } + + /** Starts the background thread that processes messages. */ + public void start() { + this.thread = new Thread(new Runnable() { + public void run() { + Peer.this.run(); + } + }); + this.thread.start(); + } + + /** + * Runs in the peers network thread and manages communication with the peer. + */ + private void run() { + assert Thread.currentThread() == thread; + // Synchronization here is unnecessary but makes FindBugs happy. + synchronized (this) { + this.running = true; + } + while (true) { + synchronized (this) { + if (!this.running) break; + } + // TODO: This isn't a good way to handle network I/O, we should probably switch to nio. + // TODO: Improve the exception handling here. + try { + Message m = conn.readMessage(); + if (m instanceof InventoryMessage) { + processInv((InventoryMessage) m); + } else if (m instanceof Block) { + processBlock((Block) m); + } else if (m instanceof AddressMessage) { + // We don't care about addresses of the network right now. But in future, + // we should save them in the wallet so we don't put too much load on the seed nodes and can + // properly explore the network. + } else { + // TODO: Handle the other messages we can receive. + LOG("Received unhandled message: " + m.toString()); + } + } catch (ProtocolException e) { + if (!(e.getCause() instanceof SocketException)) { + LOG(e.toString()); + e.printStackTrace(); + } else { + // Time to die ... + break; + } + } catch (IOException e) { + LOG(e.toString()); + e.printStackTrace(); + break; + } + } + synchronized (this) { + running = false; + } + } + + // This tracks whether we have received a block we could not connect to the chain in this session. + private boolean hasSeenUnconnectedBlock = false; + + private void processBlock(Block m) throws IOException { + assert Thread.currentThread() == thread; + try { + // Was this block requested by getBlock()? + synchronized (pendingGetDataFutures) { + for (int i = 0; i < pendingGetDataFutures.size(); i++) { + GetDataFuture f = pendingGetDataFutures.get(i); + if (Arrays.equals(f.getItem().hash, m.getHash())) { + // Yes, it was. So pass it through the future. + f.setResult(m); + // Blocks explicitly requested don't get sent to the block chain. + pendingGetDataFutures.remove(i); + return; + } + } + } + // Otherwise it's a block sent to us because the peer thought we needed it, so add it to the block chain. + // This call will synchronize on blockChain. + if (blockChain.add(m)) { + // The block was successfully linked into the chain. + if (hasSeenUnconnectedBlock && blockChain.getUnconnectedBlock() == null) { + // We cleared out our unconnected blocks. This likely means block chain download is "done" in the + // sense that we were downloading blocks as part of the chained download, + // and there's no more to come. To some extent of course the download is never done. + LOG("Block chain download done."); + if (chainCompletionLatch != null) { + chainCompletionLatch.countDown(); + chainCompletionLatch = null; + } + hasSeenUnconnectedBlock = false; + } + } else { + // This block is unconnected - we don't know how to get from it back to the genesis block yet. That + // must mean that there are blocks we are missing, so do another getblocks with a new block locator + // to ask the peer to send them to us. This can happen during the initial block chain download where + // the peer will only send us 500 at a time and then sends us the head block expecting us to request + // the others. + + // TODO: Should actually request root of orphan chain here. + hasSeenUnconnectedBlock = true; + blockChainDownload(m.getHash()); + } + } catch (VerificationException e) { + // We don't want verification failures to kill the thread. + LOG("Ignoring verification exception."); + } catch (ScriptException e) { + // We don't want script failures to kill the thread. + e.printStackTrace(); + } + } + + private void processInv(InventoryMessage inv) throws IOException { + assert Thread.currentThread() == thread; + // The peer told us about some blocks or transactions they have. For now we only care about blocks. + // Note that as we don't actually want to store the entire block chain or even the headers of the block + // chain, we may end up requesting blocks we already requested before. This shouldn't (in theory) happen + // enough to be a problem. + Block topBlock = blockChain.getUnconnectedBlock(); + byte[] topHash = (topBlock != null ? topBlock.getHash() : null); + if (inv.items.size() == 1 && inv.items.get(0).type == InventoryItem.Type.Block && topHash != null && + Arrays.equals(inv.items.get(0).hash, topHash)) { + // An inv with a single hash containing our most recent unconnected block is a special inv, + // it's kind of like a tickle from the peer telling us that it's time to download more blocks to catch up to + // the block chain. We could just ignore this and treat it as a regular inv but then we'd download the head + // block over and over again after each batch of 500 blocks, which is wasteful. + blockChainDownload(topHash); + return; + } + InventoryMessage getdata = new InventoryMessage(params); + for (InventoryItem item : inv.items) { + if (item.type != InventoryItem.Type.Block) continue; + getdata.items.add(item); + } + // No blocks to download. This probably contained transactions instead, but right now we can't prove they are + // valid so we don't bother downloading transactions that aren't in blocks yet. + if (getdata.items.size() == 0) + return; + // This will cause us to receive a bunch of block messages. + conn.writeMessage(NetworkConnection.MSG_GETDATA, getdata); + } + + /** + * Asks the connected peer for the block of the given hash, and returns a Future representing the answer. + * If you want the block right away and don't mind waiting for it, just call .get() on the result. Your thread + * will block until the peer answers. You can also use the Future object to wait with a timeout, or just check + * whether it's done later. + * + * @param blockHash Hash of the block you wareare requesting. + * @throws IOException + */ + public Future getBlock(byte[] blockHash) throws IOException { + InventoryMessage getdata = new InventoryMessage(params); + InventoryItem inventoryItem = new InventoryItem(InventoryItem.Type.Block, blockHash); + getdata.items.add(inventoryItem); + GetDataFuture future = new GetDataFuture(inventoryItem); + // Add to the list of things we're waiting for. It's important this come before the network send to avoid + // race conditions. + synchronized (pendingGetDataFutures) { + pendingGetDataFutures.add(future); + } + conn.writeMessage(NetworkConnection.MSG_GETDATA, getdata); + return future; + } + + // A GetDataFuture wraps the result of a getBlock or (in future) getTransaction so the owner of the object can + // decide whether to wait forever, wait for a short while or check later after doing other work. + private class GetDataFuture implements Future { + private boolean cancelled; + private InventoryItem item; + private CountDownLatch latch; + private T result; + + GetDataFuture(InventoryItem item) { + this.item = item; + this.latch = new CountDownLatch(1); + } + + public boolean cancel(boolean b) { + // Cannot cancel a getdata - once sent, it's sent. + cancelled = true; + return false; + } + + public boolean isCancelled() { + return cancelled; + } + + public boolean isDone() { + return result != null || cancelled; + } + + public T get() throws InterruptedException, ExecutionException { + latch.await(); + assert result != null; + return result; + } + + public T get(long l, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException { + if (!latch.await(l, timeUnit)) + throw new TimeoutException(); + assert result != null; + return result; + } + + InventoryItem getItem() { + return item; + } + + /** Called by the Peer when the result has arrived. Completes the task. */ + void setResult(T result) { + assert Thread.currentThread() == thread; // Called from peer thread. + this.result = result; + // Now release the thread that is waiting. We don't need to synchronize here as the latch establishes + // a memory barrier. + latch.countDown(); + } + } + + /** + * Send the given Transaction, ie, make a payment with BitCoins. To create a transaction you can broadcast, use + * a {@link Wallet}. After the broadcast completes, confirm the send using the wallet confirmSend() method. + * @throws IOException + */ + public void broadcastTransaction(Transaction tx) throws IOException { + conn.writeMessage(NetworkConnection.MSG_TX, tx); + } + + private void blockChainDownload(byte[] toHash) throws IOException { + // This may run in ANY thread. + + // The block chain download process is a bit complicated. Basically, we start with zero or more blocks in a + // chain that we have from a previous session. We want to catch up to the head of the chain BUT we don't know + // where that chain is up to or even if the top block we have is even still in the chain - we + // might have got ourselves onto a fork that was later resolved by the network. + // + // To solve this, we send the peer a block locator which is just a list of block hashes. It contains the + // blocks we know about, but not all of them, just enough of them so the peer can figure out if we did end up + // on a fork and if so, what the earliest still valid block we know about is likely to be. + // + // Once it has decided which blocks we need, it will send us an inv with up to 500 block messages. We may + // have some of them already if we already have a block chain and just need to catch up. Once we request the + // last block, if there are still more to come it sends us an "inv" containing only the hash of the head + // block. + // + // That causes us to download the head block but then we find (in processBlock) that we can't connect + // it to the chain yet because we don't have the intermediate blocks. So we rerun this function building a + // new block locator describing where we're up to. + // + // The getblocks with the new locator gets us another inv with another bunch of blocks. We download them once + // again. This time when the peer sends us an inv with the head block, we already have it so we won't download + // it again - but we recognize this case as special and call back into blockChainDownload to continue the + // process. + // + // So this is a complicated process but it has the advantage that we can download a chain of enormous length + // in a relatively stateless manner and with constant/bounded memory usage. + + // TODO: Block locators should be abstracted out rather than special cased here. + List blockLocator = new LinkedList(); + // We don't do the exponential thinning here, so if we get onto a fork of the chain we will end up + // redownloading the whole thing again. + blockLocator.add(params.genesisBlock.getHash()); + Block topBlock = blockChain.getTopBlock(); + if (topBlock != null) { + blockLocator.add(0, topBlock.getHash()); + } + GetBlocksMessage message = new GetBlocksMessage(params, blockLocator, toHash); + conn.writeMessage(NetworkConnection.MSG_GETBLOCKS, message); + } + + /** + * Starts an asynchronous download of the block chain. Completion of the download is a somewhat vague concept in + * BitCoin as the chain is constantly growing, but essentially we deem the download complete once we have + * received the block that the peer told us was the head when we first started the download. + * + * @return a {@link CountDownLatch} that can be used to wait until the chain download is "complete". + */ + public CountDownLatch startBlockChainDownload() throws IOException { + chainCompletionLatch = new CountDownLatch(1); + blockChainDownload(params.genesisBlock.getHash()); + return chainCompletionLatch; + } + + /** + * Terminates the network connection and stops the background thread. + */ + public void disconnect() { + synchronized (this) { + running = false; + } + try { + // This will cause the background thread to die, but it's really ugly. We must do a better job of this. + conn.shutdown(); + } catch (IOException e) { + // Don't care about this. + } + } +} diff --git a/src/com/google/bitcoin/core/PeerAddress.java b/src/com/google/bitcoin/core/PeerAddress.java new file mode 100644 index 000000000..b554caa02 --- /dev/null +++ b/src/com/google/bitcoin/core/PeerAddress.java @@ -0,0 +1,90 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Date; + +import static com.google.bitcoin.core.Utils.uint32ToByteStreamLE; +import static com.google.bitcoin.core.Utils.uint64ToByteStreamLE; + +/** + * A PeerAddress holds an IP address and port number representing the network location of + * a peer in the BitCoin P2P network. It exists primarily for serialization purposes. + */ +public class PeerAddress extends Message { + private static final long serialVersionUID = 7501293709324197411L; + + InetAddress addr; + int port; + BigInteger services; + + public PeerAddress(NetworkParameters params, byte[] payload, int offset) throws ProtocolException { + super(params, payload, offset); + } + + public PeerAddress(InetAddress addr, int port) { + this.addr = addr; + this.port = port; + } + + public void bitcoinSerializeToStream(OutputStream stream) throws IOException { + int secs = (int)(new Date().getTime() / 1000); + uint32ToByteStreamLE(secs, stream); + uint64ToByteStreamLE(BigInteger.ZERO, stream); + // Java does not provide any utility to map an IPv4 address into IPv6 space, so we have to do it by hand. + byte[] ipBytes = addr.getAddress(); + if (ipBytes.length == 4) { + byte[] v6addr = new byte[16]; + System.arraycopy(ipBytes, 0, v6addr, 12, 4); + v6addr[10] = (byte) 0xFF; + v6addr[11] = (byte) 0xFF; + ipBytes = v6addr; + } + stream.write(ipBytes); + // And write out the port. + stream.write((byte) (0xFF & port)); + stream.write((byte) (0xFF & (port >> 8))); + } + + @Override + protected void parse() throws ProtocolException { + // Format of a serialized address: + // uint32 timestamp + // uint64 services (flags determining what the node can do) + // 16 bytes ip address + // 2 bytes port num + long time = readUint32(); + services = readUint64(); + byte[] addrBytes = readBytes(16); + try { + addr = InetAddress.getByAddress(addrBytes); + } catch (UnknownHostException e) { + throw new RuntimeException(e); // Cannot happen. + } + port = ((0xFF & bytes[cursor++]) << 8) | (0xFF & bytes[cursor++]); + } + + @Override + public String toString() { + return "[" + addr.getHostAddress() + "]:" + port; + } +} diff --git a/src/com/google/bitcoin/core/ProtocolException.java b/src/com/google/bitcoin/core/ProtocolException.java new file mode 100644 index 000000000..634d7cefb --- /dev/null +++ b/src/com/google/bitcoin/core/ProtocolException.java @@ -0,0 +1,33 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +@SuppressWarnings("serial") +public class ProtocolException extends Exception { + + public ProtocolException(String msg) { + super(msg); + } + + public ProtocolException(Exception e) { + super(e); + } + + public ProtocolException(String msg, Exception e) { + super(msg, e); + } +} diff --git a/src/com/google/bitcoin/core/Script.java b/src/com/google/bitcoin/core/Script.java new file mode 100644 index 000000000..308b046b6 --- /dev/null +++ b/src/com/google/bitcoin/core/Script.java @@ -0,0 +1,397 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import com.google.bitcoin.core.Transaction.SigHash; +import com.google.bitcoin.bouncycastle.util.Arrays; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +import static com.google.bitcoin.core.Utils.LOG; +import static com.google.bitcoin.core.Utils.bytesToHexString; + +/** + * BitCoin transactions don't specify what they do directly. Instead a + * small binary stack language is used to define programs that when evaluated return whether the transaction + * "accepts" or rejects the other transactions connected to it.

    + * + * This implementation of the scripting language is incomplete. It contains enough support to run standard + * transactions generated by the official client, but non-standard transactions will fail. + */ +public class Script { + // Some constants used for decoding the scripts. + public static final int OP_PUSHDATA1 = 76; + public static final int OP_PUSHDATA2 = 77; + public static final int OP_PUSHDATA4 = 78; + public static final int OP_DUP = 118; + public static final int OP_HASH160 = 169; + public static final int OP_EQUALVERIFY = 136; + public static final int OP_CHECKSIG = 172; + + byte[] program; + private int cursor; + + // The stack consists of an ordered series of data buffers growing from zero up. + private Stack stack; + // The program is a set of byte[]s where each element is either [opcode] or [data, data, data ...] + private List chunks; + private boolean tracing; + byte[] programCopy; // TODO: remove this + private NetworkParameters params; + + /** Concatenates two scripts to form a new one. This is used when verifying transactions. */ + public static Script join(Script a, Script b) throws ScriptException { + assert a.params == b.params; + byte[] fullProg = new byte[a.programCopy.length + b.programCopy.length]; + System.arraycopy(a.programCopy, 0, fullProg, 0, a.programCopy.length); + System.arraycopy(b.programCopy, 0, fullProg, a.programCopy.length, b.programCopy.length); + return new Script(a.params, fullProg, 0, fullProg.length); + } + + /** + * Construct a Script using the given network parameters and a range of the programBytes array. + * @param params Network parameters. + * @param programBytes Array of program bytes from a transaction. + * @param offset How many bytes into programBytes to start reading from. + * @param length How many bytes to read. + * @throws ScriptException + */ + public Script(NetworkParameters params, byte[] programBytes, int offset, int length) throws ScriptException { + this.params = params; + stack = new Stack(); + parse(programBytes, offset, length); + } + + /** If true, running a program will log its instructions. */ + public void setTracing(boolean value) { + this.tracing = value; + } + + /** Returns the program opcodes as a string, for example "[1234] DUP HAHS160" */ + public String toString() { + StringBuffer buf = new StringBuffer(); + for (byte[] chunk : chunks) { + if (chunk.length == 1) { + String opName; + int opcode = 0xFF & chunk[0]; + switch (opcode) { + case OP_DUP: opName = "DUP"; break; + case OP_HASH160: opName = "HASH160"; break; + case OP_CHECKSIG: opName = "CHECKSIG"; break; + case OP_EQUALVERIFY: opName = "EQUALVERIFY"; break; + default: + opName = "?(" + opcode + ")"; + break; + } + buf.append(opName); + buf.append(" "); + } else { + // Data chunk + buf.append("["); + buf.append(chunk.length); + buf.append("]"); + buf.append(bytesToHexString(chunk)); + buf.append(" "); + } + } + return buf.toString(); + } + + + private byte[] getData(int len) throws ScriptException { + try { + byte[] buf = new byte[len]; + System.arraycopy(program, cursor, buf, 0, len); + cursor += len; + return buf; + } catch (ArrayIndexOutOfBoundsException e) { + // We want running out of data in the array to be treated as a handleable script parsing exception, + // not something that abnormally terminates the app. + throw new ScriptException("Failed read of " + len + " bytes", e); + } + } + + private int readByte() { + return 0xFF & program[cursor++]; + } + + /** + * To run a script, first we parse it which breaks it up into chunks representing pushes of + * data or logical opcodes. Then we can run the parsed chunks. + * + * The reason for this split, instead of just interpreting directly, is to make it easier + * to reach into a programs structure and pull out bits of data without having to run it. + * This is necessary to render the to/from addresses of transactions in a user interface. + * The official client does something similar. + */ + private void parse(byte[] programBytes, int offset, int length) throws ScriptException { + // TODO: this is inefficient + programCopy = new byte[length]; + System.arraycopy(programBytes, offset, programCopy, 0, length); + + program = programCopy; + offset = 0; + chunks = new ArrayList(10); // Arbitrary choice of initial size. + cursor = offset; + while (cursor < offset + length) { + int opcode = readByte(); + if (opcode >= 0xF0) { + // Not a single byte opcode. + opcode = (opcode << 8) | readByte(); + } + + if (opcode > 0 && opcode < OP_PUSHDATA1) { + // Read some bytes of data, where how many is the opcode value itself. + chunks.add(getData(opcode)); // opcode == len here. + } else if (opcode == OP_PUSHDATA1) { + int len = readByte(); + chunks.add(getData(len)); + } else if (opcode == OP_PUSHDATA2) { + // Read a short, then read that many bytes of data. + int len = readByte() | (readByte() << 8); + chunks.add(getData(len)); + } else if (opcode == OP_PUSHDATA4) { + // Read a uint32, then read that many bytes of data. + LOG("PUSHDATA4: Unimplemented"); + } else { + chunks.add(new byte[] { (byte) opcode }); + } + } + } + + /** + * Returns true if this transaction is of a format that means it was a direct IP to IP transaction. These + * transactions are deprecated and no longer used, support for creating them has been removed from the official + * client. + */ + public boolean isSentToIP() { + if (chunks.size() != 2) + return false; + return (0xFF & chunks.get(1)[0]) == OP_CHECKSIG && chunks.get(0).length > 1; + } + + /** + * If a program matches the standard template DUP HASH160 EQUALVERIFY CHECKSIG + * then this function retrieves the third element, otherwise it throws a ScriptException. + * + * This is useful for fetching the destination address of a transaction. + */ + public byte[] getPubKeyHash() throws ScriptException { + if (chunks.size() != 5) + throw new ScriptException("Script not of right size to be a scriptPubKey, " + + "expecting 5 but got " + chunks.size()); + if ((0xFF & chunks.get(0)[0]) != OP_DUP || + (0xFF & chunks.get(1)[0]) != OP_HASH160 || + (0xFF & chunks.get(3)[0]) != OP_EQUALVERIFY || + (0xFF & chunks.get(4)[0]) != OP_CHECKSIG) + throw new ScriptException("Script not in the standard scriptPubKey form"); + + // Otherwise, the third element is the hash of the public key, ie the bitcoin address. + return chunks.get(2); + } + + /** + * If a program has two data buffers (constants) and nothing else, the second one is returned. + * For a scriptSig this should be the public key of the sender. + * + * This is useful for fetching the source address of a transaction. + */ + public byte[] getPubKey() throws ScriptException { + if (chunks.size() == 1) { + // Direct IP to IP transactions only have the public key in their scriptSig. + return chunks.get(0); + } + if (chunks.size() != 2) + throw new ScriptException("Script not of right size to be a scriptSig, expecting 2" + + " but got " + chunks.size()); + if (!(chunks.get(0).length > 1) && (chunks.get(1).length > 1)) + throw new ScriptException("Script not in the standard scriptSig form: " + + chunks.size() + " chunks"); + return chunks.get(1); + } + + /** + * Convenience wrapper around getPubKey. Only works for scriptSigs. + */ + public Address getFromAddress() throws ScriptException { + return new Address(params, Utils.sha256hash160(getPubKey())); + } + + + /** + * Gets the destination address from this script, if it's in the required form (see getPubKey). + * @throws ScriptException + */ + public Address getToAddress() throws ScriptException { + return new Address(params, getPubKeyHash()); + } + + /** + * Runs the script with the given Transaction as the "context". Some operations like CHECKSIG + * require a transaction to operate on (eg to hash). The context transaction is typically + * the transaction having its inputs verified, ie the one where the scriptSig comes from. + */ + public boolean run(Transaction context) throws ScriptException { + for (byte[] chunk : chunks) { + if (chunk.length == 1) { + int opcode = 0xFF & chunk[0]; + switch (opcode) { + case OP_DUP: opDup(); break; + case OP_HASH160: opHash160(); break; + case OP_EQUALVERIFY: opEqualVerify(); break; + case OP_CHECKSIG: opCheckSig(context); break; + default: + if (tracing) LOG("Unknown/unimplemented opcode: " + opcode); + } + } else { + // Data block, push it onto the stack. + if (tracing) LOG("Push " + Utils.bytesToHexString(chunk)); + stack.add(chunk); + } + } + byte[] result = stack.pop(); + if (result.length != 1) + throw new ScriptException("Script left junk at the top of the stack: " + Utils.bytesToHexString(result)); + return result[0] == 1; + } + + void logStack() { + for (int i = 0; i < stack.size(); i++) { + LOG("Stack[" + i + "]: " + Utils.bytesToHexString(stack.get(i))); + } + } + + // WARNING: Unfinished and untested! + @SuppressWarnings("unused") + private void opCheckSig( Transaction context) throws ScriptException { + byte[] pubkey = stack.pop(); + byte[] sigAndHashType = stack.pop(); + // The signature has an extra byte on the end to indicate the type of hash. The signature + // is over the contents of the program, minus the signature itself of course. + byte hashType = sigAndHashType[sigAndHashType.length - 1]; + // The high bit of the hashType byte is set to indicate "anyone can pay". + boolean anyoneCanPay = hashType < 0; + // Mask out the top bit. + hashType &= (byte)-1 >>> 1; + Transaction.SigHash sigHash; + switch (hashType) { + case 1: sigHash = SigHash.ALL; break; + case 2: sigHash = SigHash.NONE; break; + case 3: sigHash = SigHash.SINGLE; break; + default: + // TODO: This should probably not be an exception. + throw new ScriptException("Unknown sighash byte: " + sigAndHashType[sigAndHashType.length - 1]); + } + + byte[] sig = new byte[sigAndHashType.length - 1]; + System.arraycopy(sigAndHashType, 0, sig, 0, sig.length); + + if (tracing) LOG("CHECKSIG: hashtype=" + sigHash.toString() + " anyoneCanPay=" + anyoneCanPay); + if (context == null) { + // TODO: Fix the unit tests to run scripts in transaction context then remove this. + pushBool(true); + return; + } + // TODO: Implement me! + // Transaction tx = context.simplify(sigHash, 0, anyoneCanPay); + + // The steps to do so are as follows: + // - Use the hashtype to fiddle the transaction as appropriate + // - Serialize the transaction and hash it + // - Use EC code to verify the hash matches the signature + pushBool(true); + } + + private void pushBool(boolean val) { + stack.push(new byte[] { val ? (byte)1 : (byte)0 }); + } + + private void opEqualVerify() throws ScriptException { + if (tracing) LOG("EQUALVERIFY"); + byte[] a = stack.pop(); + byte[] b = stack.pop(); + if (!Arrays.areEqual(a, b)) + throw new ScriptException("EQUALVERIFY failed: " + Utils.bytesToHexString(a) + " vs " + + Utils.bytesToHexString(b)); + } + + /** Replaces the top item in the stack with a hash160 of it */ + private void opHash160() { + byte[] buf = stack.pop(); + byte[] hash = Utils.sha256hash160(buf); + stack.add(hash); + if (tracing) LOG("HASH160: output is " + Utils.bytesToHexString(hash)); + } + + /** Duplicates the top item on the stack */ + private void opDup() { + if (tracing) LOG("DUP"); + stack.add(Arrays.clone(stack.lastElement())); + } + + ////////////////////// Interface for writing scripts from scratch //////////////////////////////// + + /** Writes out the given byte buffer to the output stream with the correct opcode prefix */ + static void writeBytes(OutputStream os, byte[] buf) throws IOException { + if (buf.length < OP_PUSHDATA1) { + os.write(buf.length); + os.write(buf); + } else if (buf.length < 256) { + os.write(OP_PUSHDATA1); + os.write(buf.length); + os.write(buf); + } else if (buf.length < 65536) { + os.write(OP_PUSHDATA2); + os.write(0xFF & (buf.length)); + os.write(0xFF & (buf.length >> 8)); + } else { + throw new RuntimeException("Unimplemented"); + } + } + + static byte[] createOutputScript(Address to) { + try { + // TODO: Do this by creating a Script *first* then having the script reassemble itself into bytes. + ByteArrayOutputStream bits = new ByteArrayOutputStream(); + bits.write(OP_DUP); + bits.write(OP_HASH160); + writeBytes(bits, to.getHash160()); + bits.write(OP_EQUALVERIFY); + bits.write(OP_CHECKSIG); + return bits.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + + static byte[] createInputScript(byte[] signature, byte[] pubkey) { + try { + // TODO: Do this by creating a Script *first* then having the script reassemble itself into bytes. + ByteArrayOutputStream bits = new ByteArrayOutputStream(); + writeBytes(bits, signature); + writeBytes(bits, pubkey); + return bits.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/com/google/bitcoin/core/ScriptException.java b/src/com/google/bitcoin/core/ScriptException.java new file mode 100644 index 000000000..256adcc0b --- /dev/null +++ b/src/com/google/bitcoin/core/ScriptException.java @@ -0,0 +1,29 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +@SuppressWarnings("serial") +public class ScriptException extends Exception { + + public ScriptException(String msg) { + super(msg); + } + + public ScriptException(String msg, Exception e) { + super(msg, e); + } +} diff --git a/src/com/google/bitcoin/core/Transaction.java b/src/com/google/bitcoin/core/Transaction.java new file mode 100644 index 000000000..d3b570ccf --- /dev/null +++ b/src/com/google/bitcoin/core/Transaction.java @@ -0,0 +1,364 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static com.google.bitcoin.core.Utils.*; + +/** + * A transaction represents the movement of coins from some addresses to some other addresses. It can also represent + * the minting of new coins. A Transaction object corresponds to the equivalent in the BitCoin C++ implementation.

    + * + * It implements TWO serialization protocols - the BitCoin proprietary format which is identical to the C++ + * implementation and is used for reading/writing transactions to the wire and for hashing. It also implements Java + * serialization which is used for the wallet. This allows us to easily add extra fields used for our own accounting + * or UI purposes. + */ +public class Transaction extends Message implements Serializable { + private static final long serialVersionUID = -8567546957352643140L; + + // These are serialized in both bitcoin and java serialization. + long version; + ArrayList inputs; + ArrayList outputs; + long lockTime; + + // This is an in memory helper only. + transient byte[] hash; + + Transaction(NetworkParameters params) { + super(params); + version = 1; + inputs = new ArrayList(); + outputs = new ArrayList(); + } + + /** + * Creates a transaction from the given serialized bytes, eg, from a block or a tx network message. + */ + public Transaction(NetworkParameters params, byte[] payloadBytes) throws ProtocolException { + super(params, payloadBytes, 0); + } + + /** + * Creates a transaction by reading payload starting from offset bytes in. Length of a transaction is fixed. + */ + public Transaction(NetworkParameters params, byte[] payload, int offset) throws ProtocolException { + super(params, payload, offset); + // inputs/outputs will be created in parse() + } + + /** + * Returns a read-only list of the inputs of this transaction. + */ + public List getInputs() { + return Collections.unmodifiableList(inputs); + } + + /** + * Returns the transaction hash as you see them in the block explorer. + */ + public byte[] getHash() { + if (hash == null) { + byte[] bits = bitcoinSerialize(); + hash = reverseBytes(doubleDigest(bits)); + } + return hash; + } + + public String getHashAsString() { + return Utils.bytesToHexString(getHash()); + } + + void setFakeHashForTesting(byte[] hash) { + this.hash = hash; + } + + /** + * Returns the sum of the outputs that are sending coins to a key in our wallet. + */ + public BigInteger getValueSentToMe(Wallet wallet) { + BigInteger v = BigInteger.ZERO; + for (TransactionOutput o : outputs) { + if (o.isMine(wallet)) { + v = v.add(o.getValue()); + } + } + return v; + } + + /** + * These constants are a part of a scriptSig signature on the inputs. They define the details of how a + * transaction can be redeemed, specifically, they control how the hash of the transaction is calculated. + * + * Note: in the official client, this enum also has another flag, SIGHASH_ANYONECANPAY. In this implementation, + * that's kept separate. + * + * Also note: only SIGHASH_ALL is actually used in the official client today. + */ + public enum SigHash { + ALL, // 1 + NONE, // 2 + SINGLE, // 3 + } + + void parse() throws ProtocolException { + version = readUint32(); + // First come the inputs. + long numInputs = readVarInt(); + inputs = new ArrayList((int)numInputs); + for (long i = 0; i < numInputs; i++) { + TransactionInput input = new TransactionInput(params, bytes, cursor); + inputs.add(input); + cursor += input.getMessageSize(); + } + // Now the outputs + long numOutputs = readVarInt(); + outputs = new ArrayList((int)numOutputs); + for (long i = 0; i < numOutputs; i++) { + TransactionOutput output = new TransactionOutput(params, this, bytes, cursor); + outputs.add(output); + cursor += output.getMessageSize(); + } + lockTime = readUint32(); + + // Store a hash, it may come in useful later (want to avoid reserialization costs). + hash = reverseBytes(doubleDigest(bytes, offset, cursor - offset)); + } + + /** + * A coinbase transaction is one that creates a new coin. They are the first transaction in each block and their + * value is determined by a formula that all implementations of BitCoin share. In 2011 the value of a coinbase + * transaction is 50 coins, but in future it will be less. A coinbase transaction is defined not only by its + * position in a block but by the data in the inputs. + */ + public boolean isCoinBase() { + return inputs.get(0).isCoinBase(); + } + + /** + * @return A human readable version of the transaction useful for debugging. + */ + public String toString() { + StringBuffer s = new StringBuffer(); + if (isCoinBase()) { + String script = "???"; + String script2 = "???"; + try { + script = inputs.get(0).getScriptSig().toString(); + script2 = outputs.get(0).getScriptPubKey().toString(); + } catch (ScriptException e) {} + return " == COINBASE TXN (scriptSig " + script + ") (scriptPubKey " + script2 + ")"; + } + for (TransactionInput in : inputs) { + s.append(" "); + s.append("from "); + + try { + s.append(in.getScriptSig().getFromAddress().toString()); + } catch (Exception e) { + s.append("[exception: ").append(e.getMessage()).append("]"); + throw new RuntimeException(e); + } + s.append("\n"); + } + for (TransactionOutput out : outputs) { + s.append(" "); + s.append("to "); + try { + Address toAddr = new Address(params, out.getScriptPubKey().getPubKeyHash()); + s.append(toAddr.toString()); + s.append(" "); + s.append(bitcoinValueToFriendlyString(out.getValue())); + s.append(" BTC"); + } catch (Exception e) { + s.append("[exception: ").append(e.getMessage()).append("]"); + } + s.append("\n"); + } + return s.toString(); + } + + /** + * Adds an input to this transaction that imports value from the given output. Note that this input is NOT + * complete and after every input is added with addInput() and every output is added with addOutput(), + * signInputs() must be called to finalize the transaction and finish the inputs off. Otherwise it won't be + * accepted by the network. + */ + public void addInput(TransactionOutput from) { + inputs.add(new TransactionInput(params, from)); + } + + /** + * Adds the given output to this transaction. The output must be completely initialized. + */ + public void addOutput(TransactionOutput to) { + to.parentTransaction = this; + outputs.add(to); + } + + /** + * Once a transaction has some inputs and outputs added, the signatures in the inputs can be calculated. The + * signature is over the transaction itself, to prove the redeemer actually created that transaction, + * so we have to do this step last. + * + * This method is similar to SignatureHash in script.cpp + * + * @param hashType This should always be set to SigHash.ALL currently. Other types are unused. + * @param wallet A wallet is required to fetch the keys needed for signing. + */ + public void signInputs(SigHash hashType, Wallet wallet) throws ScriptException { + assert inputs.size() > 0; + assert outputs.size() > 0; + + // I don't currently have an easy way to test other modes work, as the official client does not use them. + assert hashType == SigHash.ALL; + + // The transaction is signed with the input scripts empty except for the input we are signing. In the case + // where addInput has been used to set up a new transaction, they are already all empty. The input being signed + // has to have the connected OUTPUT program in it when the hash is calculated! + // + // Note that each input may be claiming an output sent to a different key. So we have to look at the outputs + // to figure out which key to sign with. + + byte[][] signatures = new byte[inputs.size()][]; + ECKey[] signingKeys = new ECKey[inputs.size()]; + for (int i = 0; i < inputs.size(); i++) { + TransactionInput input = inputs.get(i); + assert input.scriptBytes.length == 0 : "Attempting to sign a non-fresh transaction"; + // Set the input to the script of its output. + input.scriptBytes = input.outpoint.getConnectedPubKeyScript(); + // Find the signing key we'll need to use. + byte[] connectedPubKeyHash = input.outpoint.getConnectedPubKeyHash(); + ECKey key = wallet.findKeyFromPubHash(connectedPubKeyHash); + // This assert should never fire. If it does, it means the wallet is inconsistent. + assert key != null : "Transaction exists in wallet that we cannot redeem: " + Utils.bytesToHexString(connectedPubKeyHash); + // Keep the key around for the script creation step below. + signingKeys[i] = key; + // The anyoneCanPay feature isn't used at the moment. + boolean anyoneCanPay = false; + byte[] hash = hashTransactionForSignature(hashType, anyoneCanPay); + Utils.LOG(" signInputs hash=" + Utils.bytesToHexString(hash)); + // Set the script to empty again for the next input. + input.scriptBytes = TransactionInput.EMPTY_ARRAY; + + // Now sign for the output so we can redeem it. We use the keypair to sign the hash, + // and then put the resulting signature in the script along with the public key (below). + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bos.write(key.sign(hash)); + bos.write((hashType.ordinal() + 1) | (anyoneCanPay ? 0x80 : 0)) ; + signatures[i] = bos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + + // Now we have calculated each signature, go through and create the scripts. Reminder: the script consists of + // a signature (over a hash of the transaction) and the complete public key needed to sign for the connected + // output. + for (int i = 0; i < inputs.size(); i++) { + TransactionInput input = inputs.get(i); + assert input.scriptBytes.length == 0; + ECKey key = signingKeys[i]; + input.scriptBytes = Script.createInputScript(signatures[i], key.getPubKey()); + } + + // Every input is now complete. + } + + private byte[] hashTransactionForSignature( SigHash type, boolean anyoneCanPay) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bitcoinSerializeToStream(bos); + // We also have to write a hash type. + int hashType = type.ordinal() + 1; + if (anyoneCanPay) + hashType |= 0x80; + Utils.uint32ToByteStreamLE(hashType, bos); + // Note that this is NOT reversed to ensure it will be signed correctly. If it were to be printed out + // however then we would expect that it is IS reversed. + return doubleDigest(bos.toByteArray()); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + + /** + * Given a named input and the transaction output it connects to, runs the script formed from the + * concatenation of the input and output scripts, returning true if the link is valid. In + * this way, we prove that the creator of this transaction is allowed to redeem the output + * of the connectedTx and thus spend the money.

    + * + * WARNING: NOT FINISHED

    + * + * @param inputIndex Which input to verify. + * @param connectedTx The Transaction that the input is connected to. + */ + @SuppressWarnings("unused") + public boolean verifyInput(int inputIndex, Transaction connectedTx) throws ScriptException { + TransactionInput input = inputs.get(inputIndex); + //int outputIndex = (int) input.outpoint.index; + //assert outputIndex >= 0 && outputIndex < connectedTx.outputs.size(); + //Script outScript = connectedTx.outputs.get(outputIndex).getScriptPubKey(); + Script inScript = input.getScriptSig(); + //Script script = Script.join(inScript, outScript); + //if (script.run(this)) { + // LOG("Transaction input successfully verified!"); + // return true; + //} + byte[] pubkey = inScript.getPubKey(); + + return false; + } + + @Override + public void bitcoinSerializeToStream(OutputStream stream) throws IOException { + uint32ToByteStreamLE(version, stream); + stream.write(new VarInt(inputs.size()).encode()); + for (TransactionInput in : inputs) + in.bitcoinSerializeToStream(stream); + stream.write(new VarInt(outputs.size()).encode()); + for (TransactionOutput out : outputs) + out.bitcoinSerializeToStream(stream); + uint32ToByteStreamLE(lockTime, stream); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Transaction)) return false; + Transaction t = (Transaction) other; + + byte[] hash1 = t.getHash(); + byte[] hash2 = getHash(); + return Arrays.equals(hash2, hash1); + } + + @Override + public int hashCode() { + return Arrays.hashCode(hash); + } +} diff --git a/src/com/google/bitcoin/core/TransactionInput.java b/src/com/google/bitcoin/core/TransactionInput.java new file mode 100644 index 000000000..ad080479c --- /dev/null +++ b/src/com/google/bitcoin/core/TransactionInput.java @@ -0,0 +1,127 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Serializable; + +/** + * A transfer of coins from one address to another creates a transaction in which the outputs + * can be claimed by the recipient in the input of another transaction. You can imagine a + * transaction as being a module which is wired up to others, the inputs of one have to be wired + * to the outputs of another. The exceptions are coinbase transactions, which create new coins. + */ +public class TransactionInput extends Message implements Serializable { + private static final long serialVersionUID = -7687665228438202968L; + // An apparently unused field intended for altering transactions after they were broadcast. + long sequence; + // The output of the transaction we're gathering coins from. + + TransactionOutPoint outpoint; + // The "script bytes" might not actually be a script. In coinbase transactions where new coins are minted there + // is no input transaction, so instead the scriptBytes contains some extra stuff (like a rollover nonce) that we + // don't care about much. The bytes are turned into a Script object (cached below) on demand via a getter. + byte[] scriptBytes; + // The Script object obtained from parsing scriptBytes. Only filled in on demand and if the transaction is not + // coinbase. + transient private Script scriptSig; + + static public final byte[] EMPTY_ARRAY = new byte[0]; + + /** Used only in creation of the genesis block. */ + TransactionInput(NetworkParameters params, byte[] scriptBytes) { + super(params); + this.scriptBytes = scriptBytes; + this.outpoint = new TransactionOutPoint(params, -1, null); + this.sequence = 0xFFFFFFFFL; + } + + /** Creates an UNSIGNED input that links to the given output */ + TransactionInput(NetworkParameters params, TransactionOutput output) { + super(params); + long outputIndex = output.getIndex(); + outpoint = new TransactionOutPoint(params, outputIndex, output.parentTransaction); + scriptBytes = EMPTY_ARRAY; + this.sequence = 0xFFFFFFFFL; + } + + /** Deserializes an input message. This is usually part of a transaction message. */ + public TransactionInput(NetworkParameters params, byte[] payload, int offset) throws ProtocolException { + super(params, payload, offset); + } + + void parse() throws ProtocolException { + outpoint = new TransactionOutPoint(params, bytes, cursor); + cursor += outpoint.getMessageSize(); + int scriptLen = (int) readVarInt(); + scriptBytes = readBytes(scriptLen); + sequence = readUint32(); + } + + @Override + public void bitcoinSerializeToStream(OutputStream stream) throws IOException { + outpoint.bitcoinSerializeToStream(stream); + stream.write(new VarInt(scriptBytes.length).encode()); + stream.write(scriptBytes); + Utils.uint32ToByteStreamLE(sequence, stream); + } + + /** + * Coinbase transactions have special inputs with hashes of zero. If this is such an input, returns true. + */ + public boolean isCoinBase() { + for (int i = 0; i < outpoint.hash.length; i++) + if (outpoint.hash[i] != 0) return false; + return true; + } + + /** + * Returns the input script. + */ + public Script getScriptSig() throws ScriptException { + // Transactions that generate new coins don't actually have a script. Instead this + // parameter is overloaded to be something totally different. + if (scriptSig == null) { + assert scriptBytes != null; + scriptSig = new Script(params, scriptBytes, 0, scriptBytes.length); + } + return scriptSig; + } + + /** + * Convenience method that returns the from address of this input by parsing the scriptSig. + * @throws ScriptException if the scriptSig could not be understood (eg, if this is a coinbase transaction). + */ + public Address getFromAddress() throws ScriptException { + assert !isCoinBase(); + return getScriptSig().getFromAddress(); + } + + + /** Returns a human readable debug string. */ + public String toString() { + if (isCoinBase()) + return "TxIn: COINBASE"; + try { + return "TxIn from " + Utils.bytesToHexString(getScriptSig().getPubKey()) + " script:" + + getScriptSig().toString(); + } catch (ScriptException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/com/google/bitcoin/core/TransactionOutPoint.java b/src/com/google/bitcoin/core/TransactionOutPoint.java new file mode 100644 index 000000000..e2beacf1f --- /dev/null +++ b/src/com/google/bitcoin/core/TransactionOutPoint.java @@ -0,0 +1,92 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Serializable; + +/** + * This message is effectively a reference or pointer to a transaction output. + */ +public class TransactionOutPoint extends Message implements Serializable { + // ID of the transaction to which we refer. + + byte[] hash; + // Which output of that transaction we are talking about. + long index; + + // This is not part of bitcoin serialization. + Transaction fromTx; + + TransactionOutPoint(NetworkParameters params, long index, Transaction fromTx) { + super(params); + this.index = index; + if (fromTx != null) { + this.hash = fromTx.getHash(); + this.fromTx = fromTx; + } else { + // This happens when constructing the genesis block. + hash = new byte[32]; // All zeros. + } + } + + /** Deserializes the message. This is usually part of a transaction message. */ + public TransactionOutPoint(NetworkParameters params, byte[] payload, int offset) throws ProtocolException { + super(params, payload, offset); + } + + @Override + void parse() throws ProtocolException { + hash = readHash(); + index = readUint32(); + } + + @Override + public void bitcoinSerializeToStream(OutputStream stream) throws IOException { + assert hash.length == 32; + stream.write(Utils.reverseBytes(hash)); + Utils.uint32ToByteStreamLE(index, stream); + } + + /** + * If this transaction was created using the explicit constructor rather than deserialized, + * retrieves the connected output transaction. Asserts if there is no connected transaction. + * @return + */ + TransactionOutput getConnectedOutput() { + assert fromTx != null; + return fromTx.outputs.get((int)index); + } + + /** + * Returns the pubkey script from the connected output. + */ + byte[] getConnectedPubKeyScript() { + byte[] result = getConnectedOutput().getScriptBytes(); + assert result != null; + assert result.length > 0; + return result; + } + + /** + * Convenience method to get the connected outputs pubkey hash. + */ + byte[] getConnectedPubKeyHash() throws ScriptException { + return getConnectedOutput().getScriptPubKey().getPubKeyHash(); + } +} diff --git a/src/com/google/bitcoin/core/TransactionOutput.java b/src/com/google/bitcoin/core/TransactionOutput.java new file mode 100644 index 000000000..c6a88cb98 --- /dev/null +++ b/src/com/google/bitcoin/core/TransactionOutput.java @@ -0,0 +1,134 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Arrays; + +/** + * A TransactionOutput message contains a scriptPubKey that controls who is able to spend its value. It is a sub-part + * of the Transaction message. + */ +public class TransactionOutput extends Message implements Serializable { + private static final long serialVersionUID = -590332479859256824L; + + // A transaction output has some value and a script used for authenticating that the redeemer is allowed to spend + // this output. + private BigInteger value; + private byte[] scriptBytes; + + // The script bytes are parsed and turned into a Script on demand. + private transient Script scriptPubKey; + + // This field is Java serialized but not BitCoin serialized. It's used for tracking purposes in our wallet only. + // If this flag is set to true, it means we have spent this outputs value and it shouldn't be used again or + // counted towards our balance. + boolean isSpent; + + // A reference to the transaction which holds this output. + Transaction parentTransaction; + + /** Deserializes a transaction output message. This is usually part of a transaction message. */ + public TransactionOutput(NetworkParameters params, Transaction parent, byte[] payload, + int offset) throws ProtocolException { + super(params, payload, offset); + parentTransaction = parent; + } + + TransactionOutput(NetworkParameters params, BigInteger value, Address to) { + super(params); + this.value = value; + this.scriptBytes = Script.createOutputScript(to); + } + + /** Used only in creation of the genesis block. */ + TransactionOutput(NetworkParameters params, byte[] scriptBytes) { + super(params); + this.scriptBytes = scriptBytes; + this.value = Utils.toNanoCoins(50, 0); + } + + public Script getScriptPubKey() throws ScriptException { + if (scriptPubKey == null) + scriptPubKey = new Script(params, scriptBytes, 0, scriptBytes.length); + return scriptPubKey; + } + + void parse() throws ProtocolException { + value = readUint64(); + int scriptLen = (int) readVarInt(); + scriptBytes = readBytes(scriptLen); + } + + @Override + public void bitcoinSerializeToStream( OutputStream stream) throws IOException { + assert scriptBytes != null; + Utils.uint64ToByteStreamLE(getValue(), stream); + // TODO: Move script serialization into the Script class, where it belongs. + stream.write(new VarInt(scriptBytes.length).encode()); + stream.write(scriptBytes); + } + + /** + * Returns the value of this output in nanocoins. This is the amount of currency that the destination address + * receives. + */ + public BigInteger getValue() { + return value; + } + + int getIndex() { + assert parentTransaction != null; + for (int i = 0; i < parentTransaction.outputs.size(); i++) { + if (parentTransaction.outputs.get(i) == this) + return i; + } + // Should never happen. + throw new RuntimeException("Output linked to wrong parent transaction?"); + } + + public byte[] getScriptBytes() { + return scriptBytes; + } + + /** Returns true if this output is to an address we have the keys for in the wallet. */ + public boolean isMine(Wallet wallet) { + try { + byte[] pubkeyHash = getScriptPubKey().getPubKeyHash(); + for (ECKey key : wallet.keychain) { + if (Arrays.equals(key.getPubKeyHash(), pubkeyHash)) + return true; + } + return false; + } catch (ScriptException e) { + throw new RuntimeException(e); + } + } + + /** Returns a human readable debug string. */ + public String toString() { + try { + return "TxOut of " + Utils.bitcoinValueToFriendlyString(value) + " to " + getScriptPubKey().getToAddress() + .toString() + " script:" + getScriptPubKey().toString(); + } catch (ScriptException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/src/com/google/bitcoin/core/UnknownMessage.java b/src/com/google/bitcoin/core/UnknownMessage.java new file mode 100644 index 000000000..4707aeb63 --- /dev/null +++ b/src/com/google/bitcoin/core/UnknownMessage.java @@ -0,0 +1,35 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +public class UnknownMessage extends Message { + private String name; + + public UnknownMessage(NetworkParameters params, String name, byte[] payloadBytes) throws ProtocolException { + super(params, payloadBytes, 0); + this.name = name; + } + + + public String toString() { + return "Unknown message [" + name + "]: " + (bytes == null ? "" : Utils.bytesToHexString(bytes)); + } + + @Override + public void parse() throws ProtocolException { + } +} diff --git a/src/com/google/bitcoin/core/Utils.java b/src/com/google/bitcoin/core/Utils.java new file mode 100644 index 000000000..8b771788a --- /dev/null +++ b/src/com/google/bitcoin/core/Utils.java @@ -0,0 +1,214 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import com.google.bitcoin.bouncycastle.crypto.digests.RIPEMD160Digest; + +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * A collection of various utility methods that are helpful for working with the BitCoin protocol. + */ +public class Utils { + /** How many nanocoins there are in a BitCoin. */ + public static final BigInteger COIN = new BigInteger("100000000", 10); + /** How many nanocoins there are in 0.01 BitCoins. */ + public static final BigInteger CENT = new BigInteger("1000000", 10); + + /** Convert an amount expressed in the way humans are used to into nanocoins. */ + public static BigInteger toNanoCoins(int coins, int cents) { + assert cents < 100; + BigInteger bi = BigInteger.valueOf(coins).multiply(COIN); + bi = bi.add(BigInteger.valueOf(cents).multiply(CENT)); + return bi; + } + + public static void uint32ToByteArrayBE(long val, byte[] out, int offset) { + out[offset + 0] = (byte) (0xFF & (val >> 24)); + out[offset + 1] = (byte) (0xFF & (val >> 16)); + out[offset + 2] = (byte) (0xFF & (val >> 8)); + out[offset + 3] = (byte) (0xFF & (val >> 0)); + } + + public static void uint32ToByteArrayLE(long val, byte[] out, int offset) { + out[offset + 0] = (byte) (0xFF & (val >> 0)); + out[offset + 1] = (byte) (0xFF & (val >> 8)); + out[offset + 2] = (byte) (0xFF & (val >> 16)); + out[offset + 3] = (byte) (0xFF & (val >> 24)); + } + + public static void uint32ToByteStreamLE(long val, OutputStream stream) throws IOException { + stream.write((int)(0xFF & (val >> 0))); + stream.write((int)(0xFF & (val >> 8))); + stream.write((int)(0xFF & (val >> 16))); + stream.write((int)(0xFF & (val >> 24))); + } + + public static void uint64ToByteStreamLE( BigInteger val, OutputStream stream) throws IOException { + byte[] bytes = val.toByteArray(); + if (bytes.length > 8) { + throw new RuntimeException("Input too large to encode into a uint64"); + } + bytes = reverseBytes(bytes); + stream.write(bytes); + if (bytes.length < 8) { + for (int i = 0; i < 8 - bytes.length; i++) + stream.write(0); + } + } + + /** + * See {@link Utils#doubleDigest(byte[],int,int)}. + */ + public static byte[] doubleDigest(byte[] input) { + return doubleDigest(input, 0, input.length); + } + + /** + * Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again. This is + * standard procedure in BitCoin. The resulting hash is in big endian form. + */ + public static byte[] doubleDigest(byte[] input, int offset, int length) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + digest.update(input, offset, length); + byte[] first = digest.digest(); + byte[] second = digest.digest(first); + return second; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + + /** + * Calculates SHA256(SHA256(byte range 1 + byte range 2)). + */ + public static byte[] doubleDigestTwoBuffers(byte[] input1, int offset1, int length1, + byte[] input2, int offset2, int length2) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + digest.update(input1, offset1, length1); + digest.update(input2, offset2, length2); + byte[] first = digest.digest(); + byte[] second = digest.digest(first); + return second; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + + /** Work around lack of unsigned types in Java. */ + public static boolean isLessThanUnsigned(long n1, long n2) { + return (n1 < n2) ^ ((n1 < 0) != (n2 < 0)); + } + + /** Returns the given byte array hex encoded. */ + public static String bytesToHexString(byte[] bytes) { + StringBuffer buf = new StringBuffer(bytes.length * 2); + for (byte b : bytes) { + String s = Integer.toString(0xFF & b, 16); + if (s.length() < 2) + buf.append('0'); + buf.append(s); + } + return buf.toString(); + } + + + /** Returns a copy of the given byte array in reverse order. */ + public static byte[] reverseBytes(byte[] bytes) { + // We could use the XOR trick here but it's easier to understand if we don't. If we find this is really a + // performance issue the matter can be revisited. + byte[] buf = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) + buf[i] = bytes[bytes.length - 1 - i]; + return buf; + } + + public static long readUint32(byte[] bytes, int offset) { + return ((bytes[offset++] & 0xFFL) << 0) | + ((bytes[offset++] & 0xFFL) << 8) | + ((bytes[offset++] & 0xFFL) << 16) | + ((bytes[offset++] & 0xFFL) << 24); + } + + public static long readUint32BE(byte[] bytes, int offset) { + return ((bytes[offset + 0] & 0xFFL) << 24) | + ((bytes[offset + 1] & 0xFFL) << 16) | + ((bytes[offset + 2] & 0xFFL) << 8) | + ((bytes[offset + 3] & 0xFFL) << 0); + } + + static void LOG(String msg) { + // Set this to true to see debug prints from the library. + if (false) { + System.out.print("BitCoin: "); + System.out.println(msg); + } + } + + /** + * Calculates RIPEMD160(SHA256(input)). This is used in Address calculations. + */ + public static byte[] sha256hash160(byte[] input) { + try { + byte[] sha256 = MessageDigest.getInstance("SHA-256").digest(input); + RIPEMD160Digest digest = new RIPEMD160Digest(); + digest.update(sha256, 0, sha256.length); + byte[] out = new byte[20]; + digest.doFinal(out, 0); + return out; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + + /** Returns the given value in nanocoins as a 0.12 type string. */ + public static String bitcoinValueToFriendlyString(BigInteger value) { + BigInteger coins = value.divide(COIN); + BigInteger cents = value.remainder(COIN); + return String.format("%d.%02d", coins.intValue(), cents.intValue() / 1000000); + } + + /** + * MPI encoded numbers are produced by the OpenSSL BN_bn2mpi function. They consist of + * a 4 byte big endian length field, followed by the stated number of bytes representing + * the number in big endian format. + */ + static BigInteger decodeMPI(byte[] mpi) { + int length = (int) readUint32BE(mpi, 0); + byte[] buf = new byte[length]; + System.arraycopy(mpi, 4, buf, 0, length); + return new BigInteger(buf); + } + + // The representation of nBits uses another home-brew encoding, as a way to represent a large + // hash value in only 32 bits. + static BigInteger decodeCompactBits(long compact) { + int size = ((int)(compact >> 24)) & 0xFF; + byte[] bytes = new byte[4 + size]; + bytes[3] = (byte) size; + if (size >= 1) bytes[4] = (byte) ((compact >> 16) & 0xFF); + if (size >= 2) bytes[5] = (byte) ((compact >> 8) & 0xFF); + if (size >= 3) bytes[6] = (byte) ((compact >> 0) & 0xFF); + return decodeMPI(bytes); + } +} diff --git a/src/com/google/bitcoin/core/VarInt.java b/src/com/google/bitcoin/core/VarInt.java new file mode 100644 index 000000000..df47c4c02 --- /dev/null +++ b/src/com/google/bitcoin/core/VarInt.java @@ -0,0 +1,83 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +public class VarInt { + public final long value; + + public VarInt(long value) { + this.value = value; + } + + // BitCoin has its own varint format, known in the C++ source as "compact size". + public VarInt(byte[] buf, int offset) { + int first = 0xFF & buf[offset]; + long val; + if (first < 253) { + // 8 bits. + val = first; + } else if (first == 253) { + // 16 bits. + val = (0xFF & buf[offset + 1]) | ((0xFF & buf[offset + 2]) << 8); + } else if (first == 254) { + // 32 bits. + val = Utils.readUint32(buf, offset + 1); + } else { + // 64 bits. + val = Utils.readUint32(buf, offset + 1) | (Utils.readUint32(buf, offset + 5) << 32); + } + this.value = val; + } + + public int getSizeInBytes() { + // Java doesn't have the actual value of MAX_INT, as all types in Java + // are signed *headsmash*. + if (value < 253) + return 1; + else if (value <= 65536) + return 3; // 1 marker + 2 data bytes + else if (value <= 4294967295L) + return 5; // 1 marker + 4 data bytes + else + return 9; // 1 marker + 8 data bytes + } + + + public byte[] encode() { + return encodeBE(); + } + + + public byte[] encodeBE() { + if (Utils.isLessThanUnsigned(value, 253)) { + return new byte[] { (byte)value }; + } else if (Utils.isLessThanUnsigned(value, 65536)) { + return new byte[] { (byte) 253, (byte) (value), (byte) (value >> 8) }; + } else if (Utils.isLessThanUnsigned(value, 4294967295L)) { + byte[] bytes = new byte[5]; + bytes[0] = (byte) 254; + Utils.uint32ToByteArrayLE(value, bytes, 1); + return bytes; + } else { + byte[] bytes = new byte[9]; + bytes[0] = (byte) 255; + Utils.uint32ToByteArrayLE(value & 0xFFFFFFFF, bytes, 1); + Utils.uint32ToByteArrayLE(value >> 32, bytes, 5); + return bytes; + } + } +} diff --git a/src/com/google/bitcoin/core/VerificationException.java b/src/com/google/bitcoin/core/VerificationException.java new file mode 100644 index 000000000..b03154cb7 --- /dev/null +++ b/src/com/google/bitcoin/core/VerificationException.java @@ -0,0 +1,24 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +@SuppressWarnings("serial") +public class VerificationException extends Exception { + public VerificationException(String msg) { + super(msg); + } +} diff --git a/src/com/google/bitcoin/core/VersionMessage.java b/src/com/google/bitcoin/core/VersionMessage.java new file mode 100644 index 000000000..2a60434a6 --- /dev/null +++ b/src/com/google/bitcoin/core/VersionMessage.java @@ -0,0 +1,93 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class VersionMessage extends Message { + public static final int PROTOCOL_VERSION = 31800; + + public int clientVersion; + // Flags defining what the other side supports. Right now there's only one flag and it's + // always set 1 by the official client, but we have to set it to zero as we don't store + // the block chain. In future there may be more services bits. + public int localServices = 0; + public BigInteger time; + + public VersionMessage(NetworkParameters params, byte[] msg) throws ProtocolException { + super(params, msg, 0); + } + + public VersionMessage(NetworkParameters params) { + super(params); + clientVersion = PROTOCOL_VERSION; + localServices = 0; + time = BigInteger.valueOf(System.currentTimeMillis() / 1000); + } + + @Override + public void parse() throws ProtocolException { + // There is probably a more Java-ish way to do this. + clientVersion = (int) readUint32(); + localServices = (int) readUint32(); + time = readUint64(); + // The next fields are: + // CAddress my address + // CAddress their address + // uint64 localHostNonce (random data) + // string subVer (currently "") + // int bestHeight (size of known block chain). + // + // However, we don't care about these fields right now. + } + + + @Override + public void bitcoinSerializeToStream(OutputStream buf) throws IOException { + Utils.uint32ToByteStreamLE(clientVersion, buf); + Utils.uint32ToByteStreamLE(localServices, buf); + long ltime = time.longValue(); + Utils.uint32ToByteStreamLE(ltime >> 32, buf); + Utils.uint32ToByteStreamLE(ltime, buf); + try { + // Now there are two address structures. Note that the official client doesn't do anything with these, and + // finding out your own external IP address is kind of tricky anyway, so we just serialize nonsense here. + + // My address. + new PeerAddress(InetAddress.getLocalHost(), params.port).bitcoinSerializeToStream(buf); + // Their address. + new PeerAddress(InetAddress.getLocalHost(), params.port).bitcoinSerializeToStream(buf); + } catch (UnknownHostException e) { + throw new RuntimeException(e); // Can't happen. + } catch (IOException e) { + throw new RuntimeException(e); // Can't happen. + } + // Next up is the "local host nonce", this is to detect the case of connecting + // back to yourself. We don't care about this as we won't be accepting inbound + // connections. + Utils.uint32ToByteStreamLE(0, buf); + Utils.uint32ToByteStreamLE(0, buf); + // Now comes an empty string. + buf.write(0); + // Size of known block chain. Claim we never saw any blocks. + Utils.uint32ToByteStreamLE(0, buf); + } +} diff --git a/src/com/google/bitcoin/core/Wallet.java b/src/com/google/bitcoin/core/Wallet.java new file mode 100644 index 000000000..3e3e7c216 --- /dev/null +++ b/src/com/google/bitcoin/core/Wallet.java @@ -0,0 +1,346 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import java.io.*; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * A Wallet stores keys and a record of transactions that have not yet been spent. Thus, it is capable of + * providing transactions on demand that meet a given combined value. Once a transaction + * output is used, it is removed from the wallet as it is no longer available for spending.

    + * + * The Wallet is read and written from disk, so be sure to follow the Java serialization + * versioning rules here. We use the built in Java serialization to avoid the need to + * pull in a potentially large (code-size) third party serialization library.

    + */ +public class Wallet implements Serializable { + private static final long serialVersionUID = -4501424466753895784L; + + // A list of transactions with outputs we can spend. Note that some of these transactions may be partially spent, + // that is, they have outputs some of which are redeemed and others which aren't already. The spentness of each + // output is tracked in the TransactionOutput object. The value of all unspent outputs is the balance of the + // wallet. + public final ArrayList unspent; + + // When all the outputs of a transaction are spent, it gets put here. These transactions aren't useful for + // anything except record keeping and presentation to the user. + private final LinkedList fullySpent; + + // A list of public/private EC keys owned by this user. + public final ArrayList keychain; + private final NetworkParameters params; + + transient private ArrayList eventListeners; + + /** + * Creates a new, empty wallet with no keys and no transactions. If you want to restore a wallet from disk instead, + * see loadFromFile. + */ + public Wallet(NetworkParameters params) { + this.params = params; + keychain = new ArrayList(); + unspent = new ArrayList(); + fullySpent = new LinkedList(); + eventListeners = new ArrayList(); + } + + /** + * Uses Java serialization to save the wallet to the given file. + */ + public synchronized void saveToFile(File f) throws IOException { + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f)); + oos.writeObject(this); + oos.close(); + } + + /** + * Returns a wallet deserialized from the given file. + */ + static public Wallet loadFromFile(File f) throws IOException { + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(new FileInputStream(f)); + return (Wallet) ois.readObject(); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } finally { + if (ois != null) ois.close(); + } + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + eventListeners = new ArrayList(); + } + + /** + * Returns true if the given transaction is present in the wallet, comparing by hash value (not by object + * reference). So you can create a transaction object from scratch and get true from this method if the + * transaction is logically equal. + */ + public synchronized boolean isTransactionPresent(Transaction transaction) { + for (Transaction tx : unspent) { + if (Arrays.equals(tx.getHash(), transaction.getHash())) return true; + } + for (Transaction tx : fullySpent) { + if (Arrays.equals(tx.getHash(), transaction.getHash())) return true; + } + return false; + } + + /** + * Called by the {@link BlockChain} when we receive a new block that sends coins to one of our addresses, + * stores the transaction in the wallet so we can spend it in future. Don't call this on transactions we already + * have, for instance because we created them ourselves! + */ + synchronized void receive(Transaction tx) throws VerificationException { + // Runs in a peer thread. + BigInteger prevBalance = getBalance(); + + // We need to check if this transaction is spending one of our own previous transactions. This allows us to + // build up a record of our balance by reading the block chain from scratch. Other than making testing easier + // this will be useful if one day we want to support importing keypairs from a wallet.db + for (TransactionInput input : tx.inputs) { + for (int i = 0; i < unspent.size(); i++) { + Transaction t = unspent.get(i); + if (!Arrays.equals(input.outpoint.hash, t.getHash())) continue; + if (input.outpoint.index > t.outputs.size()) { + throw new VerificationException("Invalid tx connection for " + + Utils.bytesToHexString(tx.getHash())); + } + TransactionOutput linkedOutput = t.outputs.get((int) input.outpoint.index); + assert !linkedOutput.isSpent : "Double spend was accepted by network?"; + Utils.LOG("Saw a record of me spending " + Utils.bitcoinValueToFriendlyString(linkedOutput.getValue()) + + " BTC"); + linkedOutput.isSpent = true; + // Are all the outputs on this TX that are mine now spent? Note that some of the outputs may not + // be mine and thus we don't care about them. + int myOutputs = 0; + int mySpentOutputs = 0; + for (TransactionOutput output : t.outputs) { + if (!output.isMine(this)) continue; + myOutputs++; + if (output.isSpent) + mySpentOutputs++; + } + if (myOutputs == mySpentOutputs) { + // All the outputs we can claim on this TX are gone now. So remove it from the unspent list + // so future transaction processing is faster. + unspent.remove(i); + i--; // Adjust the counter so we are still in the right place after removal. + // Keep around a record of the now useless TX in case we need it in future. + fullySpent.add(t); + } + } + } + Utils.LOG("Received " + Utils.bitcoinValueToFriendlyString(tx.getValueSentToMe(this))); + unspent.add(tx); + Utils.LOG("Balance is now: " + Utils.bitcoinValueToFriendlyString(getBalance())); + + // Inform anyone interested that we have new coins. Note: we may be re-entered by the event listener, + // so we must not make assumptions about our state after this loop returns! For example, + // the balance we just received might already be spent! + for (WalletEventListener l : eventListeners) { + synchronized (l) { + l.onCoinsReceived(this, tx, prevBalance, getBalance()); + } + } + } + + /** + * Adds an event listener object. Methods on this object are called when something interesting happens, + * like receiving money.

    + * + * Threading: Event listener methods are dispatched on library provided threads and the both the wallet and the + * listener objects are locked during dispatch, so your listeners do not have to be thread safe. However they + * should not block as the Peer will be unresponsive to network traffic whilst your listener is running. + */ + public synchronized void addEventListener(WalletEventListener listener) { + eventListeners.add(listener); + } + + /** + * Call this when we have successfully transmitted the send tx to the network, to update the wallet. + */ + synchronized void confirmSend(Transaction tx) { + // This tx is supposed to be fresh, it's an error to confirmSend on a transaction that was already sent. + assert !unspent.contains(tx); + assert !fullySpent.contains(tx); + // Mark each connected output of the tx as spent, so we don't try and spend it again. + for (TransactionInput input : tx.inputs) { + TransactionOutput connectedOutput = input.outpoint.getConnectedOutput(); + assert !connectedOutput.isSpent : "createSend called before corresponding confirmSend"; + connectedOutput.isSpent = true; + } + // Some of the outputs probably send coins back to us, eg for change or because this transaction is just + // consolidating the wallet. Mark any output that is NOT back to us as spent, + // then add this TX to the wallet so we can show it in the UI later and use it for further spending. + try { + int numSpentOutputs = 0; + for (TransactionOutput output : tx.outputs) { + if (findKeyFromPubHash(output.getScriptPubKey().getToAddress().getHash160()) == null) { + // This output didn't go to us, so by definition it is now spent. + assert !output.isSpent; + output.isSpent = true; + numSpentOutputs++; + } + } + if (numSpentOutputs == tx.outputs.size()) { + // All of the outputs are to other people, so this transaction isn't useful anymore for further + // spending. Stick it in a different section of the wallet so it doesn't slow down creating future + // spend transactions. + fullySpent.add(tx); + } else { + unspent.add(tx); + } + } catch (ScriptException e) { + // This cannot happen - we made this script so we should be able to parse it. + throw new RuntimeException(e); + } + } + + /** + * Creates a transaction that sends the given number of nanocoins to address. The change is sent to the first + * address in the wallet, so you must have added at least one key. + */ + synchronized Transaction createSend(Address address, BigInteger nanocoins) { + // For now let's just pick the first key in our keychain. In future we might want to do something else to + // give the user better privacy here, eg in incognito mode. + assert keychain.size() > 0 : "Can't send value without an address to use for receiving change"; + ECKey first = keychain.get(0); + return createSend(address, nanocoins, first.toAddress(params)); + } + + /** + * Sends coins to the given address, via the given {@link Peer}. Change is returned to the first key in the wallet. + * @param to Which address to send coins to. + * @param nanocoins How many nanocoins to send. You can use Utils.toNanoCoins() to calculate this. + * @return The {@link Transaction} that was created or null if there was insufficient balance to send the coins. + * @throws IOException if there was a problem broadcasting the transaction + */ + public synchronized Transaction sendCoins(Peer peer, Address to, BigInteger nanocoins) throws IOException { + Transaction tx = createSend(to, nanocoins); + if (tx == null) // Not enough money! :-( + return null; + peer.broadcastTransaction(tx); + confirmSend(tx); + return tx; + } + + /** + * Creates a transaction that sends $coins.$cents BTC to the given address.

    + * + * IMPORTANT: This method does NOT update the wallet. If you call createSend again you may get two transactions + * that spend the same coins. You have to call confirmSend on the created transaction to prevent this, + * but that should only occur once the transaction has been accepted by the network. This implies you cannot have + * more than one outstanding sending tx at once. + * + * @param address The BitCoin address to send the money to. + * @param nanocoins How much currency to send, in nanocoins. + * @param changeAddress Which address to send the change to, in case we can't make exactly the right value from + * our coins. This should be an address we own (is in the keychain). + * @return a new {@link Transaction} or null if we cannot afford this send. + */ + + synchronized Transaction createSend(Address address, BigInteger nanocoins, Address changeAddress) { + Utils.LOG("Creating send tx to " + address.toString() + " for " + + Utils.bitcoinValueToFriendlyString(nanocoins)); + // To send money to somebody else, we need to do the following: + // - Gather up transactions with unspent outputs until we have sufficient value. + // TODO: Sort coins so we use the smallest first, to combat wallet fragmentation. + BigInteger valueGathered = BigInteger.ZERO; + List gathered = new LinkedList(); + for (Transaction tx : unspent) { + for (TransactionOutput output : tx.outputs) { + if (output.isSpent) continue; + if (!output.isMine(this)) continue; + gathered.add(output); + valueGathered = valueGathered.add(output.getValue()); + } + if (valueGathered.compareTo(nanocoins) >= 0) break; + } + // Can we afford this? + if (valueGathered.compareTo(nanocoins) < 0) { + Utils.LOG("Insufficient value in wallet for send, missing " + + Utils.bitcoinValueToFriendlyString(nanocoins.subtract(valueGathered))); + // TODO: Should throw an exception here. + return null; + } + Transaction sendTx = new Transaction(params); + sendTx.addOutput(new TransactionOutput(params, nanocoins, address)); + BigInteger change = valueGathered.subtract(nanocoins); + if (change.compareTo(BigInteger.ZERO) > 0) { + // The value of the inputs is greater than what we want to send. Just like in real life then, + // we need to take back some coins ... this is called "change". Add another output that sends the change + // back to us. + Utils.LOG(" with " + Utils.bitcoinValueToFriendlyString(change) + " coins change"); + sendTx.addOutput(new TransactionOutput(params, change, changeAddress)); + } + for (TransactionOutput output : gathered) { + sendTx.addInput(output); + } + + // Now sign the inputs, thus proving that we are entitled to redeem the connected outputs. + try { + sendTx.signInputs(Transaction.SigHash.ALL, this); + } catch (ScriptException e) { + // If this happens it means an output script in a wallet tx could not be understood. That should never + // happen, if it does it means the wallet has got into an inconsistent state. + throw new RuntimeException(e); + } + return sendTx; + } + + /** + * Adds the given ECKey to the wallet. There is currently no way to delete keys (that would result in coin loss). + */ + public synchronized void addKey(ECKey key) { + assert !keychain.contains(key); + keychain.add(key); + } + + /** + * Locates a keypair from the keychain given the hash of the public key. This is needed when finding out which + * key we need to use to redeem a transaction output. Returns null if no key was found. + */ + public synchronized ECKey findKeyFromPubHash(byte[] pubkeyHash) { + for (ECKey key : keychain) { + if (Arrays.equals(key.getPubKeyHash(), pubkeyHash)) return key; + } + return null; + } + + /** + * Returns the balance of this wallet in nanocoins by summing up all unspent outputs that were sent to us. + */ + public synchronized BigInteger getBalance() { + BigInteger balance = BigInteger.ZERO; + for (Transaction tx : unspent) { + for (TransactionOutput output : tx.outputs) { + if (output.isSpent) continue; + if (!output.isMine(this)) continue; + balance = balance.add(output.getValue()); + } + } + return balance; + } +} diff --git a/src/com/google/bitcoin/core/WalletEventListener.java b/src/com/google/bitcoin/core/WalletEventListener.java new file mode 100644 index 000000000..09bd64d10 --- /dev/null +++ b/src/com/google/bitcoin/core/WalletEventListener.java @@ -0,0 +1,21 @@ +package com.google.bitcoin.core; + +import java.math.BigInteger; + +/** + * Implementing WalletEventListener allows you to learn when a wallets balance has changed. + */ +public interface WalletEventListener { + /** + * This is called on a Peer thread when a block is received that sends some coins to you. Note that this will + * also be called when downloading the block chain as the wallet balance catches up, + * so if you don't want that register the event listener after the chain is downloaded. It's safe to use methods + * of wallet during the execution of this callback. + * + * @param wallet The wallet object that received the coins/ + * @param tx The transaction which sent us the coins. + * @param prevBalance Balance before the coins were received. + * @param newBalance Current balance of the wallet. + */ + public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance); +} diff --git a/src/com/google/bitcoin/examples/PingService.java b/src/com/google/bitcoin/examples/PingService.java new file mode 100644 index 000000000..82c79a855 --- /dev/null +++ b/src/com/google/bitcoin/examples/PingService.java @@ -0,0 +1,89 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.examples; + +import com.google.bitcoin.core.*; + +import java.io.File; +import java.io.IOException; +import java.math.BigInteger; +import java.net.InetAddress; + +/** + * PingService demonstrates basic usage of the library. It sits on the network and when it receives coins, simply + * sends them right back to the previous owner, determined rather arbitrarily by the address of the first input. + */ +public class PingService { + public static void main(String[] args) throws Exception { + final NetworkParameters params = NetworkParameters.prodNet(); + + // Try to read the wallet from storage, create a new one if not possible. + Wallet wallet; + final File walletFile = new File("pingservice.wallet"); + try { + wallet = Wallet.loadFromFile(walletFile); + } catch (IOException e) { + wallet = new Wallet(params); + wallet.keychain.add(new ECKey()); + wallet.saveToFile(walletFile); + } + // Fetch the first key in the wallet (should be the only key). + ECKey key = wallet.keychain.get(0); + + // Connect to the localhost node. + System.out.println("Please wait, connecting and downloading block chain. This may take a while."); + System.out.println("Send coins to: " + key.toAddress(params).toString()); + + NetworkConnection conn = new NetworkConnection(InetAddress.getLocalHost(), params); + BlockChain chain = new BlockChain(params, wallet); + final Peer peer = new Peer(params, conn, chain); + peer.start(); + peer.startBlockChainDownload().await(); + + // We want to know when the balance changes. + wallet.addEventListener(new WalletEventListener() { + public void onCoinsReceived(Wallet w, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { + // Running on a peer thread. + + // It's impossible to pick one specific identity that you receive coins from in BitCoin as there + // could be inputs from many addresses. So instead we just pick the first and assume they were all + // owned by the same person. + try { + TransactionInput input = tx.getInputs().get(0); + Address from = input.getFromAddress(); + BigInteger value = tx.getValueSentToMe(w); + System.out.println("Received " + Utils.bitcoinValueToFriendlyString(value) + " from " + from.toString()); + // Now send the coins back! + Transaction sendTx = w.sendCoins(peer, from, value); + assert sendTx != null; // We should never try to send more coins than we have! + System.out.println("Sent coins back! Transaction hash is " + sendTx.getHashAsString()); + w.saveToFile(walletFile); + } catch (ScriptException e) { + // If we didn't understand the scriptSig, just crash. + e.printStackTrace(); + throw new RuntimeException(e); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + }); + + System.out.println("Waiting for coins to arrive. Press Ctrl-C to quit."); + // The peer thread keeps us alive until something kills the process. + } +} diff --git a/src/com/google/bitcoin/examples/PrivateKeys.java b/src/com/google/bitcoin/examples/PrivateKeys.java new file mode 100644 index 000000000..124e7e5cc --- /dev/null +++ b/src/com/google/bitcoin/examples/PrivateKeys.java @@ -0,0 +1,67 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.examples; + +import com.google.bitcoin.core.*; + +import java.math.BigInteger; +import java.net.InetAddress; + +/** + * This example shows how to solve the challenge Hal posted here: + * + * http://www.bitcoin.org/smf/index.php?topic=3638.0 + * + * in which a private key with some coins associated with it is published. The goal is to import the private key, + * claim the coins and then send them to a different address. + * + */ +public class PrivateKeys { + public static void main(String[] args) throws Exception { + NetworkParameters params = NetworkParameters.prodNet(); + try { + // Decode the private key from Satoshis Base58 variant. + BigInteger privKey = Base58.decodeToBigInteger(args[0]); + ECKey key = new ECKey(privKey); + System.out.println("Address from private key is: " + key.toAddress(params).toString()); + // And the address ... + Address destination = new Address(params, args[1]); + + // Import the private key to a fresh wallet. + Wallet wallet = new Wallet(params); + wallet.addKey(key); + + // Find the transactions that involve those coins. + NetworkConnection conn = new NetworkConnection(InetAddress.getLocalHost(), params); + BlockChain chain = new BlockChain(params, wallet); + Peer peer = new Peer(params, conn, chain); + peer.start(); + peer.startBlockChainDownload().await(); + + // And take them! + System.out.println("Claiming " + Utils.bitcoinValueToFriendlyString(wallet.getBalance()) + " coins"); + wallet.sendCoins(peer, destination, wallet.getBalance()); + // Wait a few seconds to let the packets flush out to the network (ugly). + Thread.sleep(5000); + peer.disconnect(); + } catch (ArrayIndexOutOfBoundsException e) { + System.out.println("First arg should be private key in Base58 format. Second argument should be address " + + "to send to."); + return; + } + } +} diff --git a/tests/com/google/bitcoin/core/AddressTest.java b/tests/com/google/bitcoin/core/AddressTest.java new file mode 100644 index 000000000..ec8bcfe2f --- /dev/null +++ b/tests/com/google/bitcoin/core/AddressTest.java @@ -0,0 +1,44 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import com.google.bitcoin.bouncycastle.util.encoders.Hex; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class AddressTest { + static final NetworkParameters testParams = NetworkParameters.testNet(); + static final NetworkParameters prodParams = NetworkParameters.prodNet(); + + @Test public void testStringification() throws Exception { + // Test a testnet address. + Address a = new Address(testParams, Hex.decode("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc")); + assertEquals("n4eA2nbYqErp7H6jebchxAN59DmNpksexv", a.toString()); + + Address b = new Address(prodParams, Hex.decode("4a22c3c4cbb31e4d03b15550636762bda0baf85a")); + assertEquals("17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL", b.toString()); + } + + @Test public void testDecoding() throws Exception { + Address a = new Address(testParams, "n4eA2nbYqErp7H6jebchxAN59DmNpksexv"); + assertEquals("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc", Utils.bytesToHexString(a.getHash160())); + + Address b = new Address(prodParams, "17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL"); + assertEquals("4a22c3c4cbb31e4d03b15550636762bda0baf85a", Utils.bytesToHexString(b.getHash160())); + } +} diff --git a/tests/com/google/bitcoin/core/Base58Test.java b/tests/com/google/bitcoin/core/Base58Test.java new file mode 100644 index 000000000..d02a54a63 --- /dev/null +++ b/tests/com/google/bitcoin/core/Base58Test.java @@ -0,0 +1,38 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import junit.framework.TestCase; + +import java.math.BigInteger; +import java.util.Arrays; + +public class Base58Test extends TestCase { + public void testEncode() { + byte[] testbytes = "Hello World".getBytes(); + assertEquals("JxF12TrwUP45BMd", Base58.encode(testbytes)); + + BigInteger bi = BigInteger.valueOf(3471844090L); + assertEquals("16Ho7Hs", Base58.encode(bi.toByteArray())); + } + + public void testDecode() { + byte[] testbytes = "Hello World".getBytes(); + byte[] actualbytes = Base58.decode("JxF12TrwUP45BMd"); + assertTrue(new String(actualbytes), Arrays.equals(testbytes, actualbytes)); + } +} diff --git a/tests/com/google/bitcoin/core/BlockChainTest.java b/tests/com/google/bitcoin/core/BlockChainTest.java new file mode 100644 index 000000000..e68f55de9 --- /dev/null +++ b/tests/com/google/bitcoin/core/BlockChainTest.java @@ -0,0 +1,131 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import com.google.bitcoin.bouncycastle.util.encoders.Hex; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +// Tests still to write: +// - Rest of checkDifficultyTransitions: verify we don't accept invalid transitions. +// - Fragmented chains can be joined together. +// - Longest chain is selected based on total difficulty not length. +// - Many more ... +public class BlockChainTest { + private static final NetworkParameters params = NetworkParameters.testNet(); + private BlockChain chain; + + @Before + public void setUp() { + Wallet wallet = new Wallet(params); + chain = new BlockChain(params, wallet); + } + + @Test + public void testBasicChaining() throws Exception { + // Check that we can plug a few blocks together. + // Block 1 from the testnet. + Block b1 = getBlock1(); + assertTrue(chain.add(b1)); + // Block 2 from the testnet. + Block b2 = getBlock2(); + + // Let's try adding an invalid block. + long n = b2.nonce; + try { + // TODO: Encapsulate these fields behind setters that recalc the hash automatically. + b2.nonce = 12345; + b2.hash = null; + chain.add(b2); + // We should not get here. + assertTrue(false); + } catch (VerificationException e) { + // Pass. + b2.nonce = n; + b2.hash = null; + } + assertTrue(chain.add(b2)); + } + + @Test + public void testBadDifficulty() throws Exception { + assertTrue(chain.add(getBlock1())); + Block b2 = getBlock2(); + assertTrue(chain.add(b2)); + NetworkParameters params2 = NetworkParameters.testNet(); + Block bad = new Block(params2); + // Merkle root can be anything here, doesn't matter. + bad.merkleRoot = Hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + // Nonce was just some number that made the hash < difficulty limit set below, it can be anything. + bad.nonce = 140548933; + bad.time = 1279242649; + bad.prevBlockHash = b2.getHash(); + // We're going to make this block so easy 50% of solutions will pass, and check it gets rejected for having a + // bad difficulty target. Unfortunately the encoding mechanism means we cannot make one that accepts all + // solutions. + bad.difficultyTarget = 0x207fFFFFL; + try { + chain.add(bad); + // The difficulty target above should be rejected on the grounds of being easier than the networks + // allowable difficulty. + assertTrue(false); + } catch (VerificationException e) { + assertTrue(e.getMessage(), e.getMessage().indexOf("Difficulty target is bad") >= 0); + } + + // Accept any level of difficulty now. + params2.proofOfWorkLimit = new BigInteger + ("00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16); + try { + chain.add(bad); + // We should not get here as the difficulty target should not be changing at this point. + assertTrue(false); + } catch (VerificationException e) { + assertTrue(e.getMessage(), e.getMessage().indexOf("Unexpected change in difficulty") >= 0); + } + + // TODO: Test difficulty change is not out of range when a transition period becomes valid. + } + + private Block getBlock2() throws Exception { + Block b2 = new Block(params); + b2.merkleRoot = Hex.decode("addc858a17e21e68350f968ccd384d6439b64aafa6c193c8b9dd66320470838b"); + b2.nonce = 2642058077L; + b2.time = 1296734343; + b2.prevBlockHash = + Hex.decode("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604"); + assertEquals("000000037b21cac5d30fc6fda2581cf7b2612908aed2abbcc429c45b0557a15f", b2.getHashAsString()); + b2.verify(); + return b2; + } + + private Block getBlock1() throws Exception { + Block b1 = new Block(params); + b1.merkleRoot = Hex.decode("0e8e58ecdacaa7b3c6304a35ae4ffff964816d2b80b62b58558866ce4e648c10"); + b1.nonce = 236038445; + b1.time = 1296734340; + b1.prevBlockHash = Hex.decode("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"); + assertEquals("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604", b1.getHashAsString()); + b1.verify(); + return b1; + } +} diff --git a/tests/com/google/bitcoin/core/BlockTest.java b/tests/com/google/bitcoin/core/BlockTest.java new file mode 100644 index 000000000..1c62b296f --- /dev/null +++ b/tests/com/google/bitcoin/core/BlockTest.java @@ -0,0 +1,99 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import com.google.bitcoin.bouncycastle.util.encoders.Hex; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class BlockTest { + static final NetworkParameters params = NetworkParameters.testNet(); + + static final byte[] blockBytes; + + static { + // Block 00000000a6e5eb79dcec11897af55e90cd571a4335383a3ccfbc12ec81085935 + // One with lots of transactions in, so a good test of the merkle tree hashing. + blockBytes = Hex.decode("0100000040f11b68435988807d64dff20261f7d9827825fbb37542601fb94d45000000000f28f7c69e2669981f92ff081c129e196200c60f4fad7911d93a682de0b49ea2ecd9d24c1844011d00d361050c01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff07041844011d0142ffffffff0100f2052a01000000434104a313febd5f91b6a13bd9c5317030518fee96d1319a0eb10076917294933d09c17dc1588a06953a264738f2acea0c66b99e796caa4f28158e0dd5f6fed69a185bac000000000100000001aa18a952c3f73e5d7440bc570b2aa78f72059887b25b6a1790514b7feedec090000000008b483045022100a970ee6e96fa8bea1cf76d3bda3fb70441a6ec50014d4ea3adcdeae9fbfb5129022025ce9e090366dd6175071a0a5b4a4727571b9bd7bdd5a74d3d3bad7f63eb5dd4014104ac44bdf511477465cb70fef1d06b9241e74d26047ccbdfa641ec9a0115ad35594cbb58a61a6fd56893a405bcffbf6555995ddedc7e6cd4e5ceb83a37e1cf8f98ffffffff02004d92d86a0000001976a914b8083945473bc8289efb681f94de7b07a5b851ad88ac00743ba40b0000001976a914ef01911c9efec6799d1ee5f7c6fb072d9669da8088ac000000000100000001438bd97cb2172e0dd6f341e455e00b7d089747bd4e7f54bd802afe6a6d006c7c000000008a47304402207db94026c96572519101a08e2c864bbe51c987eda6266079a35286df68f123ca02202d7d24c616776a70cce6cb2f97a424e47c30d466e96b750ca03564810249073c014104880286646dab4c894a5ff1bf62bd80047a50b86446b326f2155de94a54d01f9058d4cbc7452563a7c18b2bfb353262fc5adac6307a9446e8c4669daa58e97071ffffffff0200743ba40b0000001976a914fce443c743b456606d1e70ff0d98c4609addc10688ac00ba1dd2050000001976a91411e3e67c08e5d791c97b3d49a8d52025d3f78d3a88ac000000000100000001dc4a6300b6eca8d7ab8e119e9fc4b18890c0e26ec950e681b8d5e46c214aee24010000008b48304502202bcf8632a11192f6b4998343c13589771e6715a080236087dcb1771cbab01809022100edcc38488dd70cd38c058994f143ca5d259071b8fe54c66bf67e55d4468dcacb01410475106e33e14e9cf35bc359dd4120b580ecf5412bb8803f2a927aecd4218d1346e242c7056dca2e4c114fcf2f60799bc5e79107bd1a8b8d5135c92f02bdb59834ffffffff0200f2052a010000001976a9146c9715e09fb00ba84af1ff916ff409b4a5dc9ae288ac00c817a8040000001976a914f7be161206700eb7be1bca5768232c61e4694f4788ac000000000100000001b6cc12ff76247895cb7a604d888012136f06bba64654262044ecb93ff7762c2f000000008b48304502206d795045622c7cdfb4a211c5b41d477920437c21e69214ab4a14f10fe0306b78022100840e55114d6922f3c5e44c7cdcf85dc800d1caef64e7846998423e4ba86714e6014104f88ae9067bc05136cb53a8c18f8549f544ff55ab87ada8f3ba7e2aea773ec73585b61f18ade1c0ddd6c447788578be5fb785c245a64d29b7ff5d28b85cbec58cffffffff0200743ba40b0000001976a914c8081083a8b741da2da260bc0656b88c7bfa6fbf88ac00743ba40b0000001976a914fce443c743b456606d1e70ff0d98c4609addc10688ac0000000001000000019a8d70c7a27560b28dfe778db9ce7f2ff235faf98d5123c07991682be90a4c16000000008b483045022100a118c34f63854ee03d15cca2918d592c295035c42e03be7b0c7e86e66d40ea790220558336d2583a1da00ed5bcad2de5d3b9d485431f702bf2f002267b35ab0b41a0014104f88ae9067bc05136cb53a8c18f8549f544ff55ab87ada8f3ba7e2aea773ec73585b61f18ade1c0ddd6c447788578be5fb785c245a64d29b7ff5d28b85cbec58cffffffff0200743ba40b0000001976a914a440ef00c2e1d39be93607da66568caa26e0501888ac00743ba40b0000001976a914e1d3e65f78f962c4e9dfd04db2119aeefa4e111088ac000000000100000001883acd4bff920f19c4e570e6b3e2d7503d1072d3ca098a124e23534ecdc879d5000000008a473044022040677305de69fd8c18e2c54d5b3c67c5c05735cf6b73d420ccd306762c4bfda2022032cd32ac15ac1820265ffce82654a6008cda22a79fb619ebb65e0af806e14f9b0141044423ef78a2859eb57c4a59dc0878141cf5a4b1fdef71d649d3fb5cf8ea6b1114f4086e5d684a0999d4435db99217a994cc3cf7ad435c8f4e44613d9d160916c4ffffffff0100743ba40b0000001976a914fce443c743b456606d1e70ff0d98c4609addc10688ac000000000100000001ceb27fb142ce3bf9a1f263653dc3971332c71dd10e0e83d647037f608c459f12000000008b4830450220389218287e87d0d7b7113eb20cc1cbf1a00d7acdca32bba7f184cd066db74d6a022100b0998058e5a242699a48f931004cf5550f4e8802b866ce1baf1a0b2616861f27014104255a048d416984101c17514a89289a7d5d3dc8c562850c7a3599f0c7c39bcf9c3a43df75e1e614e51d70c5f85212c99298a21f087be93ecba7ef3900d02c0e8bffffffff0200743ba40b0000001976a914211fd13b614521ed566ddd42738381e42c3c2b2088ac00d956345f0000001976a914d3cc345ba8bdf51d7097955f0f259731f4c34f4388ac000000000100000001703701493f08e82bf6d8cb7c517070eee9f62d14904e14636a7b4af4f34180c7010000008a4730440220061a61eae90ffcf13c10c88a88c085b02954f488823c2f5c81e83a5a833e9f3b02204a61498a9668b2793e77fe3b68585f2daff4dd5daf6097a82615035325ada4730141040db6308d6170333e2c50dee4c9f18f0ab84a7a5c4c88a6836a91f39cb8f4712e08bd72979c542d4b3b60e8dc2021c1b3cc45ffaa83f36a9dec3c4473ea2aa2f3ffffffff0200f2052a010000001976a9143e7e087b9b09149e0266b7a416da2709b4ccf58788ac00d6117e030000001976a914777af71a3b2a48e48f2e467f65028d85c1b5eb5288ac0000000001000000014bdc82abc7db9c06613a712e488685c6feb4522d25017b856222171c17d144e0000000008b4830450221009eb7edcbf8d6be63529264b07bb9f40cf1a0ca779235999e40f5311d70706f1102207f65c5f66982519e6d82e13ca3e61f4f071c73da6c5830b3c4461252012b474e0141045af9665878e6696fd069669951acc54a87c5e3b256a9e20cd8858e0dc5a8c53624e0c979096c00af8a8c60136eef9ffa3d511309417b8315b7f9e3e41e805e8fffffffff0100743ba40b0000001976a914e1d3e65f78f962c4e9dfd04db2119aeefa4e111088ac000000000100000001a854b2b84a76e43de59db647121cdfe481bd8ae9623a345c2188369775b533f7010000008c493046022100c4db6ecf679264c9b525628ec5a983710ff45a1d2d4aa0b54ee218ca9a1ad4df022100dc2e0077cfdd3cbeb28f7463632902ad5306f6d5c77c8149e5b9249bfea8060e014104f9a476b612bb9788c64b9b1e4c9d2deaae1ef0baf6eb593a95d00e2ef8a2beb897ea1fb7c3832e842dd6307fd162816c19c8f458fd8dae331dbc9062fb02e5d8ffffffff0200651b90530000001976a914d5c7c9aec292a807005f013c4d2122f7126e257788ac00743ba40b0000001976a914211fd13b614521ed566ddd42738381e42c3c2b2088ac0000000001000000012908482e9f7d31e9dd392bb6e788a329458a3bc95230b468e4b8c578d27a63b3000000008a4730440220549a7b422fc2020671acabfb937349bd87d985b2e4b9698e4ccacc985f61aee102204dc272322079e9114746db2f8d035d82b64523a69cd7be674173e063090cc8ac014104011a6c220a5549ff112c92c6c38dec93f66ef1f0a21d1409b92f0ccf0fb159aa8173a5b2413a45140fc02b45d63775bae03691d9dc87fd7a10d709a04922900cffffffff0200743ba40b0000001976a914211fd13b614521ed566ddd42738381e42c3c2b2088ac00f1dfeb470000001976a9140adcb4e90cc87f53d7618294222a8a4e193ae9f088ac00000000"); + } + + @Test + public void testBlockVerification() throws Exception { + Block block = new Block(params, blockBytes); + block.verify(); + } + + @Test + public void testBadTransactions() throws Exception { + Block block = new Block(params, blockBytes); + // Re-arrange so the coinbase transaction is not first. + Transaction tx1 = block.transactions.get(0); + Transaction tx2 = block.transactions.get(1); + block.transactions.set(0, tx2); + block.transactions.set(1, tx1); + try { + block.verify(); + assertTrue("Verified when should not.", false); + } catch (VerificationException e) { + // We should get here. + } + } + + @Test + public void testBitCoinSerialization() throws Exception { + // We have to be able to reserialize everything exactly as we found it for hashing to work. This test also + // proves that transaction serialization works, along with all its subobjects like scripts and in/outpoints. + // + // NB: This tests the BITCOIN proprietary serialization protocol. A different test checks Java serialization + // of transactions. + Block block = new Block(params, blockBytes); + assertTrue(Arrays.equals(blockBytes, block.bitcoinSerialize())); + } + + @Test + public void testJavaSerialiazation() throws Exception { + Block block = new Block(params, blockBytes); + Transaction tx = block.transactions.get(1); + + // Serialize using Java. + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(tx); + oos.close(); + byte[] javaBits = bos.toByteArray(); + // Deserialize again. + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(javaBits)); + Transaction tx2 = (Transaction) ois.readObject(); + ois.close(); + + // Note that this will actually check the transactions are equal by doing bitcoin serialization and checking + // the bytestreams are the same! A true "deep equals" is not implemented for Transaction. The primary purpose + // of this test is to ensure no errors occur during the Java serialization/deserialization process. + Utils.LOG(Utils.bytesToHexString(tx.bitcoinSerialize())); + Utils.LOG(Utils.bytesToHexString(tx2.bitcoinSerialize())); + assertEquals(tx, tx2); + } +} diff --git a/tests/com/google/bitcoin/core/ECKeyTest.java b/tests/com/google/bitcoin/core/ECKeyTest.java new file mode 100644 index 000000000..3ce82db30 --- /dev/null +++ b/tests/com/google/bitcoin/core/ECKeyTest.java @@ -0,0 +1,56 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import com.google.bitcoin.bouncycastle.util.encoders.Hex; +import org.junit.Test; + +import java.math.BigInteger; + +import static com.google.bitcoin.core.Utils.*; +import static org.junit.Assert.assertTrue; + +public class ECKeyTest { + @Test + public void testSignatures() { + // Test that we can construct an ECKey from a private key (deriving the public from the private), then signing + // a message with it. + BigInteger privkey = new BigInteger(1, Hex.decode("180cb41c7c600be951b5d3d0a7334acc7506173875834f7a6c4c786a28fcbb19")); + ECKey key = new ECKey(privkey); + byte[] message = new byte[32]; // All zeroes. + byte[] output = key.sign(message); + assertTrue(key.verify(message, output)); + + // Test interop with a signature from elsewhere. + byte[] sig = Hex.decode( + "3046022100dffbc26774fc841bbe1c1362fd643609c6e42dcb274763476d87af2c0597e89e022100c59e3c13b96b316cae9fa0ab0260612c7a133a6fe2b3445b6bf80b3123bf274d"); + assertTrue(key.verify(message, sig)); + } + + @Test + public void testASNDecode() { + byte[] privkeyASN1 = Hex.decode("3082011302010104205c0b98e524ad188ddef35dc6abba13c34a351a05409e5d285403718b93336a4aa081a53081a2020101302c06072a8648ce3d0101022100fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f300604010004010704410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8022100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141020101a144034200042af7a2aafe8dafd7dc7f9cfb58ce09bda7dce28653ab229b98d1d3d759660c672dd0db18c8c2d76aa470448e876fc2089ab1354c01a6e72cefc50915f4a963ee"); + ECKey key = ECKey.fromASN1(privkeyASN1); + byte[] message = reverseBytes(Hex.decode("11da3761e86431e4a54c176789e41f1651b324d240d599a7067bee23d328ec2a")); + byte[] output = key.sign(message); + assertTrue(key.verify(message, output)); + + output = Hex.decode + ("304502206faa2ebc614bf4a0b31f0ce4ed9012eb193302ec2bcaccc7ae8bb40577f47549022100c73a1a1acc209f3f860bf9b9f5e13e9433db6f8b7bd527a088a0e0cd0a4c83e9"); + assertTrue(key.verify(message, output)); + } +} diff --git a/tests/com/google/bitcoin/core/ScriptTest.java b/tests/com/google/bitcoin/core/ScriptTest.java new file mode 100644 index 000000000..9af530a72 --- /dev/null +++ b/tests/com/google/bitcoin/core/ScriptTest.java @@ -0,0 +1,72 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import com.google.bitcoin.bouncycastle.util.encoders.Hex; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ScriptTest { + // From tx 05e04c26c12fe408a3c1b71aa7996403f6acad1045252b1c62e055496f4d2cb1 on the testnet. + + static final String sigProg = "473 04402202b4da291cc39faf8433911988f9f49fc5c995812ca2f94db61468839c228c3e90220628bff3ff32ec95825092fa051cba28558a981fcf59ce184b14f2e215e69106701410414b38f4be3bb9fa0f4f32b74af07152b2f2f630bc02122a491137b6c523e46f18a0d5034418966f93dfc37cc3739ef7b2007213a302b7fba161557f4ad644a1c"; + + static final String pubkeyProg = "76a91433e81a941e64cda12c6a299ed322ddbdd03f8d0e88ac"; + + + static final NetworkParameters params = NetworkParameters.testNet(); + + @Test + public void testScriptSig() throws Exception { + byte[] sigProgBytes = Hex.decode(sigProg); + Script script = new Script(params, sigProgBytes, 0, sigProgBytes.length); + script.setTracing(true); + // Test we can extract the from address. + byte[] hash160 = Utils.sha256hash160(script.getPubKey()); + Address a = new Address(params, hash160); + assertEquals("mkFQohBpy2HDXrCwyMrYL5RtfrmeiuuPY2", a.toString()); + } + + @Test + public void testScriptPubKey() throws Exception { + // Check we can extract the to address + byte[] pubkeyBytes = Hex.decode(pubkeyProg); + Script pubkey = new Script(params, pubkeyBytes, 0, pubkeyBytes.length); + Address toAddr = new Address(params, pubkey.getPubKeyHash()); + assertEquals("mkFQohBpy2HDXrCwyMrYL5RtfrmeiuuPY2", toAddr.toString()); + + // To verify a transaction as legitimate, both scripts are concatenated and then the whole + // thing is run. So check we can do that. + byte[] sigBytes = Hex.decode(sigProg); + Script sig = new Script(params, sigBytes, 0, sigBytes.length); + Script allScript = Script.join(sig, pubkey); + allScript.setTracing(true); + assertTrue(allScript.run(null)); + allScript.logStack(); + } + + @Test + public void testIp() throws Exception { + byte[] bytes = Hex.decode("41043e96222332ea7848323c08116dddafbfa917b8e37f0bdf63841628267148588a09a43540942d58d49717ad3fabfe14978cf4f0a8b84d2435dad16e9aa4d7f935ac"); + Script s = new Script(params, bytes, 0, bytes.length); + s.setTracing(true); + assertTrue(s.isSentToIP()); + } +} diff --git a/tests/com/google/bitcoin/core/VarIntTest.java b/tests/com/google/bitcoin/core/VarIntTest.java new file mode 100644 index 000000000..05b2e70a8 --- /dev/null +++ b/tests/com/google/bitcoin/core/VarIntTest.java @@ -0,0 +1,43 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import junit.framework.TestCase; + +public class VarIntTest extends TestCase { + public void testVarInts() throws Exception { + VarInt a; + + // Bytes + a = new VarInt(10); + assertEquals(1, a.getSizeInBytes()); + assertEquals(1, a.encode().length); + assertEquals(10, new VarInt(a.encode(), 0).value); + + // Shorts + a = new VarInt(64000); + assertEquals(3, a.getSizeInBytes()); + assertEquals(3, a.encode().length); + assertEquals(64000, new VarInt(a.encode(), 0).value); + + a = new VarInt(0xAABBCCDDL); + assertEquals(5, a.getSizeInBytes()); + assertEquals(5, a.encode().length); + byte[] bytes = a.encode(); + assertEquals(0xAABBCCDDL, 0xFFFFFFFFL & new VarInt(bytes, 0).value); + } +} diff --git a/tests/com/google/bitcoin/core/WalletTest.java b/tests/com/google/bitcoin/core/WalletTest.java new file mode 100644 index 000000000..6a3dcd13f --- /dev/null +++ b/tests/com/google/bitcoin/core/WalletTest.java @@ -0,0 +1,141 @@ +/** + * Copyright 2011 Google Inc. + * + * Licensed 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. + */ + +package com.google.bitcoin.core; + +import org.junit.Before; +import org.junit.Test; + +import java.math.BigInteger; + +import static com.google.bitcoin.core.Utils.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class WalletTest { + static final NetworkParameters params = NetworkParameters.testNet(); + + private Address myAddress; + private Wallet wallet; + + @Before + public void setUp() { + ECKey myKey = new ECKey(); + myAddress = myKey.toAddress(params); + wallet = new Wallet(params); + wallet.addKey(myKey); + } + + private Transaction createFakeTx(BigInteger nanocoins, Address to) { + Transaction t = new Transaction(params); + TransactionOutput o1 = new TransactionOutput(params, nanocoins, to); + t.addOutput(o1); + // t1 is not a valid transaction - it has no inputs. Nonetheless, if we set it up with a fake hash it'll be + // valid enough for these tests. + byte[] hash = new byte[32]; + for (byte i = 0; i < 32; i++) hash[i] = i; + t.setFakeHashForTesting(hash); + return t; + } + + @Test + public void testBasicSpending() throws Exception { + // We'll set up a wallet that receives a coin, then sends a coin of lesser value and keeps the change. + BigInteger v1 = Utils.toNanoCoins(1, 0); + Transaction t1 = createFakeTx(v1, myAddress); + + wallet.receive(t1); + assertEquals(v1, wallet.getBalance()); + + ECKey k2 = new ECKey(); + BigInteger v2 = toNanoCoins(0, 50); + Transaction t2 = wallet.createSend(k2.toAddress(params), v2); + + // Do some basic sanity checks. + assertEquals(1, t2.inputs.size()); + LOG(t2.inputs.get(0).getScriptSig().toString()); + assertEquals(myAddress, t2.inputs.get(0).getScriptSig().getFromAddress()); + + // We have NOT proven that the signature is correct! + } + + @Test + public void testListener() throws Exception { + final Transaction fakeTx = createFakeTx(Utils.toNanoCoins(1, 0), myAddress); + final boolean[] didRun = new boolean[1]; + WalletEventListener listener = new WalletEventListener() { + public void onCoinsReceived(Wallet w, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { + assertTrue(prevBalance.equals(BigInteger.ZERO)); + assertTrue(newBalance.equals(Utils.toNanoCoins(1, 0))); + assertEquals(tx, fakeTx); // Same object. + assertEquals(w, wallet); // Same object. + didRun[0] = true; + } + }; + wallet.addEventListener(listener); + wallet.receive(fakeTx); + assertTrue(didRun[0]); + } + + @Test + public void testBalance() throws Exception { + // Receive 5 coins then half a coin. + BigInteger v1 = toNanoCoins(5, 0); + BigInteger v2 = toNanoCoins(0, 50); + Transaction t1 = createFakeTx(v1, myAddress); + Transaction t2 = createFakeTx(v2, myAddress); + BigInteger expected = toNanoCoins(5, 50); + wallet.receive(t1); + wallet.receive(t2); + assertEquals(expected, wallet.getBalance()); + + // Now spend one coin. + BigInteger v3 = toNanoCoins(1, 0); + Transaction spend = wallet.createSend(new ECKey().toAddress(params), v3); + wallet.confirmSend(spend); + // We started with 5.50 so we should have 4.50 left. + BigInteger v4 = toNanoCoins(4, 50); + assertEquals(bitcoinValueToFriendlyString(v4), + bitcoinValueToFriendlyString(wallet.getBalance())); + // And spend another coin ... + wallet.confirmSend(wallet.createSend(new ECKey().toAddress(params), v3)); + BigInteger v5 = toNanoCoins(3, 50); + assertEquals(bitcoinValueToFriendlyString(v5), + bitcoinValueToFriendlyString(wallet.getBalance())); + } + + // Intuitively you'd expect to be able to create a transaction with identical inputs and outputs and get an + // identical result to the official client. However the signatures are not deterministic - signing the same data + // with the same key twice gives two different outputs. So we cannot prove bit-for-bit compatibility in this test + // suite. + + @Test + public void testBlockChainCatchup() throws Exception { + Transaction tx1 = createFakeTx(Utils.toNanoCoins(1, 0), myAddress); + wallet.receive(tx1); + // Send 0.10 to somebody else. + Transaction send1 = wallet.createSend(new ECKey().toAddress(params), toNanoCoins(0, 10), myAddress); + // Pretend it makes it into the block chain, our wallet state is cleared but we still have the keys, and we + // want to get back to our previous state. + wallet.receive(send1); + assertEquals(bitcoinValueToFriendlyString(wallet.getBalance()), "0.90"); + // And we do it again after the catchup. + Transaction send2 = wallet.createSend(new ECKey().toAddress(params), toNanoCoins(0, 10), myAddress); + // What we'd really like to do is prove the official client would accept it .... no such luck unfortunately. + wallet.confirmSend(send2); + assertEquals(bitcoinValueToFriendlyString(wallet.getBalance()), "0.80"); + } +}